From 0e1806413654fc76548967b2889e7bfc8aaca2d5 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Fri, 5 Jul 2024 20:16:37 +0800 Subject: [PATCH 01/67] add group ring --- src/group_ring.jl | 233 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/group_ring.jl diff --git a/src/group_ring.jl b/src/group_ring.jl new file mode 100644 index 000000000..76a99315f --- /dev/null +++ b/src/group_ring.jl @@ -0,0 +1,233 @@ +import AbstractAlgebra: Ring, RingElem, add!, addeq!, base_ring, base_ring_type, + canonical_unit, characteristic, divexact, elem_type, expressify, get_cached!, + is_domain_type, is_exact_type, is_unit, isequal, mul!, parent, parent_type, zero! +import Base: + *, +, -, ==, ^, deepcopy_internal, hash, inv, isone, iszero, one, rand, show, zero + +using AbstractAlgebra +using Random: Random, GLOBAL_RNG, SamplerTrivial +using RandomExtensions: RandomExtensions, AbstractRNG, Make2 + +@attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing + base_ring::Ring + l::Int + + function PermGroupRing{T}(R::Ring, l::Int, cached::Bool) where {T<:RingElement} + return get_cached!(PermGroupRingElemID, R, cached) do + new{T}(R, l) + end::PermGroupRing{T} + end +end + +const PermGroupRingElemID = AbstractAlgebra.CacheDictType{NCRing,PermGroupRing}() + +mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem + coeffs::Dict{<:Perm,T} + parent::PermGroupRing{T} + + function PermGroupRingElem{T}(coeffs::Dict{<:Perm,T}) where {T<:RingElement} + filter!(x -> x[2] != 0, coeffs) + return new{T}(coeffs) + end + + function PermGroupRingElem{T}() where {T<:RingElement} + return new{T}(Dict{Perm,T}()) + end + + function PermGroupRingElem{T}(n::T, l::Int) where {T<:RingElement} + if iszero(n) + coeffs = Dict{Perm,T}() + else + coeffs = Dict(Perm(l) => n) + end + return new{T}(coeffs) + end + + function PermGroupRingElem{T}(p::Perm, base_ring::Ring) where {T<:RingElement} + return new{T}(Dict(p => one(base_ring))) + end +end + +# Data type and parent object methods + +parent_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = PermGroupRing{T} + +elem_type(::Type{PermGroupRing{T}}) where {T<:RingElement} = PermGroupRingElem{T} + +base_ring_type(::Type{PermGroupRing{T}}) where {T<:RingElement} = parent_type(T) + +base_ring(R::PermGroupRing) = R.base_ring::base_ring_type(R) + +parent(f::PermGroupRingElem) = f.parent + +is_domain_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_domain_type(T) + +is_exact_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_exact_type(T) + +function hash(f::PermGroupRingElem, h::UInt) + r = 0x65125ab8e0cd44ca # TODO: what to do with this? + return xor(r, hash(f.c, h)) +end + +function deepcopy_internal(f::PermGroupRingElem{T}, dict::IdDict) where {T<:RingElement} + r = PermGroupRingElem{T}(deepcopy_internal(f.coeffs, dict)) + r.parent = f.parent # parent should not be deepcopied + return r +end + +# Basic manipulation + +zero(R::PermGroupRing) = R() + +one(R::PermGroupRing) = R(1) + +iszero(f::PermGroupRingElem) = f == 0 + +isone(f::PermGroupRingElem) = f == 1 + +# Arithmetic functions + +function -(a::PermGroupRingElem{T}) where {T<:RingElement} + r = parent(a)() + for (k, v) in a.coeffs + r.coeffs[k] = -v + end + return r +end + +function +(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} + r = parent(a)() + for (k, v) in a.coeffs + r.coeffs[k] = v + end + for (k, v) in b.coeffs + if haskey(r.coeffs, k) + r.coeffs[k] += v + if r.coeffs[k] == 0 + delete!(r.coeffs, k) + end + else + r.coeffs[k] = v + end + end + return r +end + +-(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} = a + (-b) + +function *(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} + r = parent(a)() + for (k1, v1) in a.coeffs + for (k2, v2) in b.coeffs + k = k1 * k2 + if haskey(r.coeffs, k) + r.coeffs[k] += v1 * v2 + else + r.coeffs[k] = v1 * v2 + end + end + end + filter!(x -> x[2] != 0, r.coeffs) + return r +end + +# Ad hoc arithmetic functions + +*(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = a * parent(a)(n) + +*(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a * n + ++(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = a + parent(a)(n) + ++(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a + n + +*(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = a * parent(a)(p) + +*(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = parent(a)(p) * a + ++(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = a + parent(a)(p) + ++(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a + p + +# Comparison + +function ==(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} + if length(a.coeffs) != length(b.coeffs) + return false + end + for (k, v) in a.coeffs + if !haskey(b.coeffs, k) || b.coeffs[k] != v + return false + end + end + return true +end + +# Ad hoc comparison + +==(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = length(a.coeffs) == 1 && a.coeffs[Perm(parent(a).l)] == n + +==(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a == n + +==(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = length(a.coeffs) == 1 && a.coeffs[p] == base_ring(parent(a))(1) + +==(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a == p + + +# TODO Some ring interfaces, which are not required in code construction +# exact division, random generation + +# TODO Promotion rules + +# Constructors by overloading the call syntax for parent objects + +function (R::PermGroupRing{T})() where {T<:RingElement} + r = PermGroupRingElem{T}() + r.parent = R + return r +end + +function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:RingElement} + r = PermGroupRingElem{T}(coeffs) + r.parent = R + return r +end + +function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{Integer,Rational,AbstractFloat}}) where {T<:RingElement} + r = PermGroupRingElem{T}(Dict(k => base_ring(R)(v) for (k, v) in coeffs)) + r.parent = R + return r +end + +function (R::PermGroupRing{T})(n::Union{Integer,Rational,AbstractFloat}) where {T<:RingElement} + r = PermGroupRingElem{T}(base_ring(R)(n), R.l) + r.parent = R + return r +end + +function (R::PermGroupRing{T})(n::T) where {T<:RingElement} + base_ring(R) != parent(n) && error("Unable to coerce group ring element") + r = PermGroupRingElem{T}(n, l) + r.parent = R + return r +end + +function (R::PermGroupRing{T})(p::Perm) where {T<:RingElement} + r = PermGroupRingElem{T}(p, base_ring(R)) + r.parent = R + return r +end + +function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} + parent(f) != R || error("Unable to coerce group ring") + return f +end + +# TODO We may need more constructors to remove ambiguities + +# Parent constructor + +function PermutationGroupRing(R::Ring, l::Int, cached::Bool=true) + T = elem_type(R) + return PermGroupRing{T}(R, l, cached) +end From 62494934679f8e8b70d9aea2fa82f7e52f9cb512 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Fri, 5 Jul 2024 23:43:04 +0800 Subject: [PATCH 02/67] use Nemo for group ring and add lifted code --- src/ecc/ECC.jl | 6 +++++- src/ecc/codes/classical/lifted.jl | 31 +++++++++++++++++++++++++++++++ src/{ => ecc}/group_ring.jl | 12 +++++------- 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/ecc/codes/classical/lifted.jl rename src/{ => ecc}/group_ring.jl (95%) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index ed50dba5b..2b373d66f 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -16,7 +16,8 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, code_n, code_s, code_k, rate, distance, isdegenerate, faults_matrix, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, - RepCode, + PermGroupRing, PermutationGroupRing, PermGroupRingElem, + RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, Toric, Gottesman, Surface, Concat, CircuitCode, @@ -347,6 +348,7 @@ end include("circuits.jl") include("decoder_pipeline.jl") +include("group_ring.jl") include("codes/util.jl") include("codes/classical_codes.jl") @@ -363,4 +365,6 @@ include("codes/concat.jl") include("codes/random_circuit.jl") include("codes/classical/reedmuller.jl") include("codes/classical/bch.jl") +include("codes/classical/lifted.jl") + end #module diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl new file mode 100644 index 000000000..ea9276783 --- /dev/null +++ b/src/ecc/codes/classical/lifted.jl @@ -0,0 +1,31 @@ +using Nemo +import Base: zero + +struct LiftedCode{T} <: ClassicalCode + A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement + repr::Function + + function LiftedCode(A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement , repr::Function) + new{T}(A, repr) + end + + function LiftedCode(A::Union{MatrixElem{PermGroupRingElem{FqFieldElem}}, Matrix{PermGroupRingElem{FqFieldElem}}}) + # permutation representation applies only to GF(2) group algebra + @assert characteristic(A.base_ring.base_ring) == 2 + new{PermGroupRingElem{FqFieldElem}}(A, permutation_repr) + end +end + +function permutation_repr(x::PermGroupRingElem) + return sum([x.coeffs[k] .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(GF(2), parent(x).l, parent(x).l)) +end + +function parity_checks(c::LiftedCode) + vcat([hcat([c.repr(c.A[i, j]) for j in 1:size(c.A, 2)]...) for i in 1:size(c.A, 1)]...) +end + +code_n(c::LiftedCode) = nothing + +code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = size(c.A, 1) * c.A.base_ring.l + +code_k(c::LiftedCode) = nothing diff --git a/src/group_ring.jl b/src/ecc/group_ring.jl similarity index 95% rename from src/group_ring.jl rename to src/ecc/group_ring.jl index 76a99315f..720fee1fa 100644 --- a/src/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,12 +1,10 @@ -import AbstractAlgebra: Ring, RingElem, add!, addeq!, base_ring, base_ring_type, - canonical_unit, characteristic, divexact, elem_type, expressify, get_cached!, - is_domain_type, is_exact_type, is_unit, isequal, mul!, parent, parent_type, zero! import Base: *, +, -, ==, ^, deepcopy_internal, hash, inv, isone, iszero, one, rand, show, zero -using AbstractAlgebra -using Random: Random, GLOBAL_RNG, SamplerTrivial -using RandomExtensions: RandomExtensions, AbstractRNG, Make2 +using Nemo +import Nemo: Ring, RingElem, add!, addeq!, base_ring, base_ring_type, + canonical_unit, characteristic, divexact, elem_type, expressify, get_cached!, + is_domain_type, is_exact_type, is_unit, isequal, mul!, parent, parent_type, zero! @attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing base_ring::Ring @@ -19,7 +17,7 @@ using RandomExtensions: RandomExtensions, AbstractRNG, Make2 end end -const PermGroupRingElemID = AbstractAlgebra.CacheDictType{NCRing,PermGroupRing}() +const PermGroupRingElemID = Nemo.CacheDictType{NCRing,PermGroupRing}() mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem coeffs::Dict{<:Perm,T} From 6ed42eee7020ab29d7526b8d0a89850a0a108fe3 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 18:49:26 +0800 Subject: [PATCH 03/67] update code parameter of lifted codes --- src/ecc/codes/classical/lifted.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index ea9276783..75bc4e840 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -24,8 +24,10 @@ function parity_checks(c::LiftedCode) vcat([hcat([c.repr(c.A[i, j]) for j in 1:size(c.A, 2)]...) for i in 1:size(c.A, 1)]...) end -code_n(c::LiftedCode) = nothing +code_n(c::LiftedCode) = size(parity_checks(c), 2) -code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = size(c.A, 1) * c.A.base_ring.l +code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = characteristic(A.base_ring.base_ring) == 2 ? size(c.A, 2) * c.A.base_ring.l : size(c.A, 2) -code_k(c::LiftedCode) = nothing +code_s(c::LiftedCode) = rank(parity_checks(c)) + +code_k(c::LiftedCode) = code_n(c) - code_s(c) From d8dd97ca49ffc597dcb2acedb3fbb206b3e10600 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 19:15:58 +0800 Subject: [PATCH 04/67] fix lifted code parameter bugs --- src/ecc/codes/classical/lifted.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 75bc4e840..98e29b4ad 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,5 +1,4 @@ using Nemo -import Base: zero struct LiftedCode{T} <: ClassicalCode A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement @@ -16,8 +15,8 @@ struct LiftedCode{T} <: ClassicalCode end end -function permutation_repr(x::PermGroupRingElem) - return sum([x.coeffs[k] .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(GF(2), parent(x).l, parent(x).l)) +function permutation_repr(x::PermGroupRingElem{FqFieldElem}) + return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end function parity_checks(c::LiftedCode) @@ -26,7 +25,7 @@ end code_n(c::LiftedCode) = size(parity_checks(c), 2) -code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = characteristic(A.base_ring.base_ring) == 2 ? size(c.A, 2) * c.A.base_ring.l : size(c.A, 2) +code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = characteristic(c.A.base_ring.base_ring) == 2 ? size(c.A, 2) * c.A.base_ring.l : size(c.A, 2) code_s(c::LiftedCode) = rank(parity_checks(c)) From 61a26045754cdb1b03a25585ab574106779eb9a6 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 19:37:53 +0800 Subject: [PATCH 05/67] stylistic changes --- src/ecc/codes/classical/lifted.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 98e29b4ad..23e62a2cf 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,5 +1,3 @@ -using Nemo - struct LiftedCode{T} <: ClassicalCode A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement repr::Function @@ -9,8 +7,7 @@ struct LiftedCode{T} <: ClassicalCode end function LiftedCode(A::Union{MatrixElem{PermGroupRingElem{FqFieldElem}}, Matrix{PermGroupRingElem{FqFieldElem}}}) - # permutation representation applies only to GF(2) group algebra - @assert characteristic(A.base_ring.base_ring) == 2 + characteristic(A.base_ring.base_ring) == 2 && error("the default permutation representation applies only to GF(2) group algebra") new{PermGroupRingElem{FqFieldElem}}(A, permutation_repr) end end From ebb79ca76d79b3bdadb70d3faf15cc13435214f5 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 20:54:29 +0800 Subject: [PATCH 06/67] fix cached bug with other minor changes --- src/ecc/group_ring.jl | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 720fee1fa..895e6570f 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,23 +1,22 @@ import Base: - *, +, -, ==, ^, deepcopy_internal, hash, inv, isone, iszero, one, rand, show, zero + *, +, -, ==, deepcopy_internal, isone, iszero, one, zero using Nemo -import Nemo: Ring, RingElem, add!, addeq!, base_ring, base_ring_type, - canonical_unit, characteristic, divexact, elem_type, expressify, get_cached!, - is_domain_type, is_exact_type, is_unit, isequal, mul!, parent, parent_type, zero! +import Nemo: Ring, RingElem, base_ring, base_ring_type, elem_type, get_cached!, + is_domain_type, is_exact_type, parent, parent_type @attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing base_ring::Ring l::Int function PermGroupRing{T}(R::Ring, l::Int, cached::Bool) where {T<:RingElement} - return get_cached!(PermGroupRingElemID, R, cached) do + return get_cached!(PermGroupRingElemID, (R, l), cached) do new{T}(R, l) end::PermGroupRing{T} end end -const PermGroupRingElemID = Nemo.CacheDictType{NCRing,PermGroupRing}() +const PermGroupRingElemID = Nemo.CacheDictType{Tuple{Ring, Int}, NCRing}() mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem coeffs::Dict{<:Perm,T} @@ -62,11 +61,6 @@ is_domain_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_domain_ is_exact_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_exact_type(T) -function hash(f::PermGroupRingElem, h::UInt) - r = 0x65125ab8e0cd44ca # TODO: what to do with this? - return xor(r, hash(f.c, h)) -end - function deepcopy_internal(f::PermGroupRingElem{T}, dict::IdDict) where {T<:RingElement} r = PermGroupRingElem{T}(deepcopy_internal(f.coeffs, dict)) r.parent = f.parent # parent should not be deepcopied @@ -171,11 +165,8 @@ end ==(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a == p - -# TODO Some ring interfaces, which are not required in code construction -# exact division, random generation - -# TODO Promotion rules +# TODO Some functionality are expected by ring interfaces but not necessary for ECC construction, +# including show, hash, exact division, random generation, promotion rules # Constructors by overloading the call syntax for parent objects @@ -221,7 +212,7 @@ function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} return f end -# TODO We may need more constructors to remove ambiguities +# TODO We may add more constructors to remove ambiguities # Parent constructor From 142ced02cecaf510080d70ac2c5fd1b628ece790 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 20:55:12 +0800 Subject: [PATCH 07/67] stylistic changes --- src/ecc/codes/classical/lifted.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 23e62a2cf..59f7835ae 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -2,14 +2,14 @@ struct LiftedCode{T} <: ClassicalCode A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement repr::Function - function LiftedCode(A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement , repr::Function) + function LiftedCode(A::Union{MatrixElem{T}, Matrix{T}}, repr::Function) where T<:NCRingElement new{T}(A, repr) end +end - function LiftedCode(A::Union{MatrixElem{PermGroupRingElem{FqFieldElem}}, Matrix{PermGroupRingElem{FqFieldElem}}}) - characteristic(A.base_ring.base_ring) == 2 && error("the default permutation representation applies only to GF(2) group algebra") - new{PermGroupRingElem{FqFieldElem}}(A, permutation_repr) - end +function LiftedCode(A::Union{MatrixElem{PermGroupRingElem{FqFieldElem}}, Matrix{PermGroupRingElem{FqFieldElem}}}) + !(characteristic(A.base_ring.base_ring) == 2) && error("The default permutation representation applies only to GF(2) group algebra") + LiftedCode(A, permutation_repr) end function permutation_repr(x::PermGroupRingElem{FqFieldElem}) From 0798fe1a0cfc4ec45d23ac75a203d39e0f3174a1 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 7 Jul 2024 22:45:29 +0800 Subject: [PATCH 08/67] remove MatrixElem type in lifted code --- src/ecc/codes/classical/lifted.jl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 59f7835ae..8baec4e6c 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,25 +1,32 @@ -struct LiftedCode{T} <: ClassicalCode - A::Union{MatrixElem{T}, Matrix{T}} where T<:NCRingElement +struct LiftedCode{T} <: ClassicalCode where T<:NCRingElement + A::Matrix{T} repr::Function - function LiftedCode(A::Union{MatrixElem{T}, Matrix{T}}, repr::Function) where T<:NCRingElement + function LiftedCode(A::Matrix{T}, repr::Function) where T<:NCRingElement new{T}(A, repr) end end -function LiftedCode(A::Union{MatrixElem{PermGroupRingElem{FqFieldElem}}, Matrix{PermGroupRingElem{FqFieldElem}}}) - !(characteristic(A.base_ring.base_ring) == 2) && error("The default permutation representation applies only to GF(2) group algebra") +function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) + !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") + # TODO also handle the case where the base ring is not GF(2) LiftedCode(A, permutation_repr) end +LiftedCode(A::Matrix{Bool}) = LiftedCode(A, x->x) + function permutation_repr(x::PermGroupRingElem{FqFieldElem}) - return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) + return sum([Int(Nemo.lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end -function parity_checks(c::LiftedCode) - vcat([hcat([c.repr(c.A[i, j]) for j in 1:size(c.A, 2)]...) for i in 1:size(c.A, 1)]...) +function lift(repr::Function, mat::Matrix{T}) where T<:NCRingElement + vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end +lift(A::Matrix{Bool}) = A + +parity_checks(c::LiftedCode) = lift(c.repr, c.A) + code_n(c::LiftedCode) = size(parity_checks(c), 2) code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = characteristic(c.A.base_ring.base_ring) == 2 ? size(c.A, 2) * c.A.base_ring.l : size(c.A, 2) From 219b6a7d43933ff25d1f4d90df61ef113a5dcf71 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 10:04:47 +0800 Subject: [PATCH 09/67] add adjoint and fix a bug --- src/ecc/group_ring.jl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 895e6570f..87064da71 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,5 +1,4 @@ -import Base: - *, +, -, ==, deepcopy_internal, isone, iszero, one, zero +import Base: *, +, -, ==, deepcopy_internal, isone, iszero, one, zero, adjoint using Nemo import Nemo: Ring, RingElem, base_ring, base_ring_type, elem_type, get_cached!, @@ -208,7 +207,7 @@ function (R::PermGroupRing{T})(p::Perm) where {T<:RingElement} end function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} - parent(f) != R || error("Unable to coerce group ring") + parent(f) == R || error("Unable to coerce group ring") return f end @@ -220,3 +219,13 @@ function PermutationGroupRing(R::Ring, l::Int, cached::Bool=true) T = elem_type(R) return PermGroupRing{T}(R, l, cached) end + +# adjoint + +function adjoint(a::PermGroupRingElem{T}) where {T<:FqFieldElem} + r = parent(a)() + for (k, v) in a.coeffs + r.coeffs[inv(k)] = v + end + return r +end From ea61385f0d3c2ff7d56f3eb3b89a4b4402e302dc Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 10:21:00 +0800 Subject: [PATCH 10/67] remove redundant constructors and improve checkings --- src/ecc/group_ring.jl | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 87064da71..44a893b6d 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -22,26 +22,13 @@ mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem parent::PermGroupRing{T} function PermGroupRingElem{T}(coeffs::Dict{<:Perm,T}) where {T<:RingElement} - filter!(x -> x[2] != 0, coeffs) + filter!(x -> !iszero(x[2]) , coeffs) # remove zeros return new{T}(coeffs) end function PermGroupRingElem{T}() where {T<:RingElement} return new{T}(Dict{Perm,T}()) end - - function PermGroupRingElem{T}(n::T, l::Int) where {T<:RingElement} - if iszero(n) - coeffs = Dict{Perm,T}() - else - coeffs = Dict(Perm(l) => n) - end - return new{T}(coeffs) - end - - function PermGroupRingElem{T}(p::Perm, base_ring::Ring) where {T<:RingElement} - return new{T}(Dict(p => one(base_ring))) - end end # Data type and parent object methods @@ -176,6 +163,10 @@ function (R::PermGroupRing{T})() where {T<:RingElement} end function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:RingElement} + for (k,v) in coeffs + length(k.d) == R.l || error("Invalid permutation length") + parent(v) == R || error("Unable to coerce a group ring element") + end r = PermGroupRingElem{T}(coeffs) r.parent = R return r @@ -188,26 +179,28 @@ function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{Integer,Rational,Abst end function (R::PermGroupRing{T})(n::Union{Integer,Rational,AbstractFloat}) where {T<:RingElement} - r = PermGroupRingElem{T}(base_ring(R)(n), R.l) + coeffs = iszero(n) ? Dict{Perm,T}() : Dict(Perm(R.l) => base_ring(R)(n)) + r = PermGroupRingElem{T}(coeffs) r.parent = R return r end function (R::PermGroupRing{T})(n::T) where {T<:RingElement} - base_ring(R) != parent(n) && error("Unable to coerce group ring element") - r = PermGroupRingElem{T}(n, l) + base_ring(R) == parent(n) || error("Unable to coerce a group ring element") + r = PermGroupRingElem{T}(Dict(Perm(R.l) => n)) r.parent = R return r end function (R::PermGroupRing{T})(p::Perm) where {T<:RingElement} - r = PermGroupRingElem{T}(p, base_ring(R)) + length(p.d) == R.l || error("Invalid permutation length") + r = PermGroupRingElem{T}(Dict(p => one(base_ring(R)))) r.parent = R return r end function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} - parent(f) == R || error("Unable to coerce group ring") + parent(f) == R || error("Unable to coerce a group ring element") return f end From 0889b2ed3d998b61d8d9d79e0df25821e461ea14 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 10:30:37 +0800 Subject: [PATCH 11/67] fix code_n --- src/ecc/codes/classical/lifted.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 8baec4e6c..4b4d01c79 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -27,10 +27,8 @@ lift(A::Matrix{Bool}) = A parity_checks(c::LiftedCode) = lift(c.repr, c.A) -code_n(c::LiftedCode) = size(parity_checks(c), 2) +code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) -code_n(c::LiftedCode{PermGroupRingElem{FqFieldElem}}) = characteristic(c.A.base_ring.base_ring) == 2 ? size(c.A, 2) * c.A.base_ring.l : size(c.A, 2) - -code_s(c::LiftedCode) = rank(parity_checks(c)) +code_s(c::LiftedCode) = rank(parity_checks(c)) # not that they are degenagate in general code_k(c::LiftedCode) = code_n(c) - code_s(c) From 9222e895aa727b0a0279c63cc8bb0d3ec3a692e4 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 10:49:58 +0800 Subject: [PATCH 12/67] add lifted product --- src/ecc/ECC.jl | 4 +++ src/ecc/codes/lifted_product.jl | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/ecc/codes/lifted_product.jl diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 2b373d66f..68d3d110a 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -21,6 +21,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, Toric, Gottesman, Surface, Concat, CircuitCode, + LPCode, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, @@ -365,6 +366,9 @@ include("codes/concat.jl") include("codes/random_circuit.jl") include("codes/classical/reedmuller.jl") include("codes/classical/bch.jl") + +# qLDPC include("codes/classical/lifted.jl") +include("codes/lifted_product.jl") end #module diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl new file mode 100644 index 000000000..882d69d02 --- /dev/null +++ b/src/ecc/codes/lifted_product.jl @@ -0,0 +1,45 @@ +struct LPCode{T} <: AbstractECC + c₁::LiftedCode{T} + c₂::LiftedCode{T} + repr::Union{Function, Nothing} # nothing means using repr from each code respectively + + function LPCode(c₁::LiftedCode{T}, c₂::LiftedCode{T}, repr::Function) where T + new{T}(c₁, c₂, repr) + end + + function LPCode(c₁::LiftedCode{T}, c₂::LiftedCode{T}) where T + new{T}(c₁, c₂, nothing) + end +end + +function LPCode(c₁::LiftedCode{PermGroupRingElem{FqFieldElem}}, c₂::LiftedCode{PermGroupRingElem{FqFieldElem}}) + LPCode(c₁, c₂, permutation_repr) +end + +iscss(::Type{LPCode{T}}) where {T <: RingElement} = true # TODO wrong + +# In most cases, especially for `PermGroupRingElem`, +# "first product then lift" will be much more efficient, +# which requires the same matrix representation for both codes. +function parity_checks_xz(c::LPCode) + if !isnothing(c.repr) # "first product then lift" + c.c₁.A[1,1].parent == c.c₂.A[1,1].parent || error("The base rings of the two codes must be the same") + hx, hz = hgp(c.c₁.A, c.c₂.A) + # @show hx, hz + hx, hz = lift(c.repr, hx), lift(c.repr, hz) + else # fall back to the slow "first lift then product" + h₁ = lift(c.c₁.repr, parity_checks(c.c₁)) + h₂ = lift(c.c₂.repr, parity_checks(c.c₂)) + hx, hz = hgp(h₁, h₂) + end + return hx, hz +end + +parity_checks_x(c::LPCode) = parity_checks_xz(c)[1] + +parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] + +parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) + +# HGPCode = LPCode{Bool} +# or from a trivial group ring, `R = PermutationGroupRing(GF(2), 1)` From af0698a594364cdcb8d34f776a167bd21bccdf69 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 12:17:07 +0800 Subject: [PATCH 13/67] remove the wrong constructor and fix iscss --- src/ecc/codes/lifted_product.jl | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 882d69d02..5c930f978 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,37 +1,23 @@ struct LPCode{T} <: AbstractECC c₁::LiftedCode{T} c₂::LiftedCode{T} - repr::Union{Function, Nothing} # nothing means using repr from each code respectively + repr::Function function LPCode(c₁::LiftedCode{T}, c₂::LiftedCode{T}, repr::Function) where T new{T}(c₁, c₂, repr) end - - function LPCode(c₁::LiftedCode{T}, c₂::LiftedCode{T}) where T - new{T}(c₁, c₂, nothing) - end end function LPCode(c₁::LiftedCode{PermGroupRingElem{FqFieldElem}}, c₂::LiftedCode{PermGroupRingElem{FqFieldElem}}) LPCode(c₁, c₂, permutation_repr) end -iscss(::Type{LPCode{T}}) where {T <: RingElement} = true # TODO wrong +iscss(::Type{LPCode{T}}) where {T <: NCRingElement} = true -# In most cases, especially for `PermGroupRingElem`, -# "first product then lift" will be much more efficient, -# which requires the same matrix representation for both codes. function parity_checks_xz(c::LPCode) - if !isnothing(c.repr) # "first product then lift" - c.c₁.A[1,1].parent == c.c₂.A[1,1].parent || error("The base rings of the two codes must be the same") - hx, hz = hgp(c.c₁.A, c.c₂.A) - # @show hx, hz - hx, hz = lift(c.repr, hx), lift(c.repr, hz) - else # fall back to the slow "first lift then product" - h₁ = lift(c.c₁.repr, parity_checks(c.c₁)) - h₂ = lift(c.c₂.repr, parity_checks(c.c₂)) - hx, hz = hgp(h₁, h₂) - end + c.c₁.A[1,1].parent == c.c₂.A[1,1].parent || error("The base rings of the two codes must be the same") + hx, hz = hgp(c.c₁.A, c.c₂.A) + hx, hz = lift(c.repr, hx), lift(c.repr, hz) return hx, hz end From 3557282f642b0d3bc415ce582dd315f2b54c174e Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 20:27:32 +0800 Subject: [PATCH 14/67] add cyclic_permutation function and solve conflicts caused by Nemo --- src/ecc/ECC.jl | 2 +- src/ecc/codes/classical/lifted.jl | 4 +++- src/ecc/group_ring.jl | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 68d3d110a..8d5bacc7e 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -16,7 +16,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, code_n, code_s, code_k, rate, distance, isdegenerate, faults_matrix, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, - PermGroupRing, PermutationGroupRing, PermGroupRingElem, + PermGroupRing, PermutationGroupRing, PermGroupRingElem, cyclic_permutation, # group utils RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 4b4d01c79..181009704 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,3 +1,5 @@ +import Nemo: characteristic, lift, matrix_repr + struct LiftedCode{T} <: ClassicalCode where T<:NCRingElement A::Matrix{T} repr::Function @@ -16,7 +18,7 @@ end LiftedCode(A::Matrix{Bool}) = LiftedCode(A, x->x) function permutation_repr(x::PermGroupRingElem{FqFieldElem}) - return sum([Int(Nemo.lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) + return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end function lift(repr::Function, mat::Matrix{T}) where T<:NCRingElement diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 44a893b6d..c1d24f23a 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,7 +1,7 @@ import Base: *, +, -, ==, deepcopy_internal, isone, iszero, one, zero, adjoint -using Nemo -import Nemo: Ring, RingElem, base_ring, base_ring_type, elem_type, get_cached!, +import Nemo: Ring, RingElem, RingElement, NCRing, NCRingElem, NCRingElement, Perm, CacheDictType, + @attributes, base_ring, base_ring_type, elem_type, get_cached!, is_domain_type, is_exact_type, parent, parent_type @attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing @@ -15,7 +15,7 @@ import Nemo: Ring, RingElem, base_ring, base_ring_type, elem_type, get_cached!, end end -const PermGroupRingElemID = Nemo.CacheDictType{Tuple{Ring, Int}, NCRing}() +const PermGroupRingElemID = CacheDictType{Tuple{Ring, Int}, NCRing}() mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem coeffs::Dict{<:Perm,T} @@ -222,3 +222,5 @@ function adjoint(a::PermGroupRingElem{T}) where {T<:FqFieldElem} end return r end + +cyclic_permutation(n::Int, l::Int) = Perm(vcat(n+1:l, 1:n)) From 2a859cc73ea2d5cf52f2a8d6d038c370997da15d Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Mon, 8 Jul 2024 22:29:38 +0800 Subject: [PATCH 15/67] add code parameters for lifted product --- src/ecc/codes/lifted_product.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 5c930f978..4df440925 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -27,5 +27,12 @@ parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) +code_n(c::LPCode) = size(c.repr(parent(c.c₁.A[1, 1])(0)), 2) * (size(c.c₁.A, 2) * size(c.c₂.A, 2) + size(c.c₁.A, 1) * size(c.c₂.A, 1)) + +function code_k(c::LPCode) + hx, hz = parity_checks_xz(c) + code_n(c) - rank(hx) - rank(hz) # redundant rows exist +end + # HGPCode = LPCode{Bool} # or from a trivial group ring, `R = PermutationGroupRing(GF(2), 1)` From df98a8bad2da348275c9911f0eabe14e0a098a3b Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Tue, 9 Jul 2024 22:59:25 +0800 Subject: [PATCH 16/67] fix code_k bug by using mod2 rank --- src/ecc/codes/classical/lifted.jl | 8 +++++++- src/ecc/codes/lifted_product.jl | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 181009704..6404dccfc 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -31,6 +31,12 @@ parity_checks(c::LiftedCode) = lift(c.repr, c.A) code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) -code_s(c::LiftedCode) = rank(parity_checks(c)) # not that they are degenagate in general +function mod2rank(h::Matrix{<:Integer}) + Z2, _ = residue_ring(ZZ, 2) + S = matrix_space(Z2, size(h)...) + rank(S(h)) +end + +code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # note that they are degenagate in general code_k(c::LiftedCode) = code_n(c) - code_s(c) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 4df440925..3ba0eea31 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,3 +1,5 @@ +import Nemo: matrix_space + struct LPCode{T} <: AbstractECC c₁::LiftedCode{T} c₂::LiftedCode{T} @@ -29,10 +31,12 @@ parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) code_n(c::LPCode) = size(c.repr(parent(c.c₁.A[1, 1])(0)), 2) * (size(c.c₁.A, 2) * size(c.c₂.A, 2) + size(c.c₁.A, 1) * size(c.c₂.A, 1)) -function code_k(c::LPCode) +function code_s(c::LPCode) hx, hz = parity_checks_xz(c) - code_n(c) - rank(hx) - rank(hz) # redundant rows exist + mod2rank(hx) + mod2rank(hz) end +code_k(c::LPCode) = code_n(c) - code_s(c) + # HGPCode = LPCode{Bool} # or from a trivial group ring, `R = PermutationGroupRing(GF(2), 1)` From 8a905d4113f3a8ae47e5be12b63773345e029b29 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Tue, 9 Jul 2024 23:04:11 +0800 Subject: [PATCH 17/67] updated support type and related information --- src/ecc/codes/classical/lifted.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 6404dccfc..1ada5863a 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -10,13 +10,12 @@ struct LiftedCode{T} <: ClassicalCode where T<:NCRingElement end function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) + # TODO we may also want to handle the case where the base ring is not GF(2), + # such as residue_ring(ZZ, 2), which is mathematically equivalent, and residue_ring(ZZ, n) for n > 2 !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") - # TODO also handle the case where the base ring is not GF(2) LiftedCode(A, permutation_repr) end -LiftedCode(A::Matrix{Bool}) = LiftedCode(A, x->x) - function permutation_repr(x::PermGroupRingElem{FqFieldElem}) return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end @@ -25,8 +24,6 @@ function lift(repr::Function, mat::Matrix{T}) where T<:NCRingElement vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end -lift(A::Matrix{Bool}) = A - parity_checks(c::LiftedCode) = lift(c.repr, c.A) code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) From 1a3ca374de332bc8082c984340102fcac340d946 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 00:19:09 +0800 Subject: [PATCH 18/67] add BP decoder tests for LP codes --- test/test_ecc_decoder_all_setups.jl | 101 +++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index ecb19a0d5..a58ed2c54 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -5,6 +5,9 @@ using QuantumClifford.ECC import PyQDecoders import LDPCDecoders +import Nemo: GF +import LinearAlgebra + include("test_ecc_base.jl") @testset "table decoder, good for small codes" begin @@ -33,28 +36,106 @@ include("test_ecc_base.jl") end end -## +B04 = Dict() + +B04[7] = [ + 0 0 0 0; + 0 1 2 5; + 0 6 3 1 +] + +B04[9] = [ + 0 0 0 0; + 0 1 6 7; + 0 4 5 2 +] + +B04[17] = [ + 0 0 0 0; + 0 1 2 11; + 0 8 12 13 +] + +B04[19] = [ + 0 0 0 0; + 0 2 6 9; + 0 16 7 11 +] + +B118 = Dict() + +B118[16] = [ + 0 0 0 0 0; + 0 2 4 7 11; + 0 3 10 14 15 +] + +B118[21] = [ + 0 0 0 0 0; + 0 4 5 7 17; + 0 14 18 12 11 +] + +B118[30] = [ + 0 0 0 0 0; + 0 2 14 24 25; + 0 16 11 14 13 +] + +LP04 = [] +for l in keys(B04) + A = map(B04[l]) do x + (PermutationGroupRing(GF(2), l))(cyclic_permutation(x, l)) + end + push!(LP04, LPCode(LiftedCode(A), LiftedCode(A))) +end + +LP118 = [] +for l in keys(B118) + A = map(B118[l]) do x + (PermutationGroupRing(GF(2), l))(cyclic_permutation(x, l)) + end + push!(LP118, LPCode(LiftedCode(A), LiftedCode(A))) +end + +other_lifted_product_codes = [] + +l = 63 +R = PermutationGroupRing(GF(2), l) +A = zeros(R, 7,7) +A[LinearAlgebra.diagind(A)] .= R(cyclic_permutation(27, l)) +A[LinearAlgebra.diagind(A, -1)] .= R(cyclic_permutation(54, l)) +A[LinearAlgebra.diagind(A, 6)] .= R(cyclic_permutation(54, l)) +A[LinearAlgebra.diagind(A, -2)] .= R(1) +A[LinearAlgebra.diagind(A, 5)] .= R(1) + +B = zeros(R, 1, 1) +B[1,1] = (R(1) + R(cyclic_permutation(1, l)) + R(cyclic_permutation(6, l)))' + +push!(other_lifted_product_codes, LPCode(LiftedCode(A), LiftedCode(B))) + @testset "belief prop decoders, good for sparse codes" begin - codes = [ - # TODO - ] + codes = vcat(LP04, LP118, other_lifted_product_codes) noise = 0.001 setups = [ CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), + # NaiveSyndromeECCSetup(noise, 0), + # ShorSyndromeECCSetup(noise, 0), ] + # lifted product codes currently trigger errors in syndrome circuits for c in codes for s in setups for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] - e = evaluate_decoder(d(c), s, 100000) - @show c - @show s - @show e + nsamples = code_n(c)>400 ? 1000 : 10000 + # take fewer samples for larger codes to save time + e = evaluate_decoder(d(c), s, nsamples) + # @show c + # @show s + # @show e @assert max(e...) < noise/4 end end From 68baf77ca47e019c98d0b487e42c01fdc8e3b793 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 00:35:40 +0800 Subject: [PATCH 19/67] fix a type ambiguity --- src/ecc/group_ring.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index c1d24f23a..34bb57fa6 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -172,7 +172,7 @@ function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:RingElement} return r end -function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{Integer,Rational,AbstractFloat}}) where {T<:RingElement} +function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:Union{Integer,Rational,AbstractFloat}} r = PermGroupRingElem{T}(Dict(k => base_ring(R)(v) for (k, v) in coeffs)) r.parent = R return r From 3fb0be7ee98cae54351e86118ef143606125e03d Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 00:48:17 +0800 Subject: [PATCH 20/67] add reference for test LP codes and do formatting --- test/test_ecc_decoder_all_setups.jl | 61 ++++++++--------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index a58ed2c54..6bfe90f0a 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -36,51 +36,20 @@ include("test_ecc_base.jl") end end -B04 = Dict() - -B04[7] = [ - 0 0 0 0; - 0 1 2 5; - 0 6 3 1 -] - -B04[9] = [ - 0 0 0 0; - 0 1 6 7; - 0 4 5 2 -] - -B04[17] = [ - 0 0 0 0; - 0 1 2 11; - 0 8 12 13 -] - -B04[19] = [ - 0 0 0 0; - 0 2 6 9; - 0 16 7 11 -] - -B118 = Dict() - -B118[16] = [ - 0 0 0 0 0; - 0 2 4 7 11; - 0 3 10 14 15 -] - -B118[21] = [ - 0 0 0 0 0; - 0 4 5 7 17; - 0 14 18 12 11 -] - -B118[30] = [ - 0 0 0 0 0; - 0 2 14 24 25; - 0 16 11 14 13 -] +# test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 + +B04 = Dict( + 7 => [ 0 0 0 0; 0 1 2 5; 0 6 3 1], + 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], + 17 => [0 0 0 0; 0 1 2 11; 0 8 12 13], + 19 => [0 0 0 0; 0 2 6 9; 0 16 7 11] +) + +B118 = Dict( + 16 => [0 0 0 0 0; 0 2 4 7 11; 0 3 10 14 15], + 21 => [0 0 0 0 0; 0 4 5 7 17; 0 14 18 12 11], + 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], +) LP04 = [] for l in keys(B04) @@ -100,6 +69,8 @@ end other_lifted_product_codes = [] +# from https://arxiv.org/abs/2202.01702v3 + l = 63 R = PermutationGroupRing(GF(2), l) A = zeros(R, 7,7) From db4dbcf730f3082ebac517ae38326f4c6f532c5c Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 12:32:32 +0800 Subject: [PATCH 21/67] add docs for group ring --- src/ecc/group_ring.jl | 85 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 34bb57fa6..2300b2a52 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,9 +1,59 @@ -import Base: *, +, -, ==, deepcopy_internal, isone, iszero, one, zero, adjoint +import Base: *, +, -, ==, deepcopy_internal, show, isone, iszero, one, zero, adjoint import Nemo: Ring, RingElem, RingElement, NCRing, NCRingElem, NCRingElement, Perm, CacheDictType, @attributes, base_ring, base_ring_type, elem_type, get_cached!, is_domain_type, is_exact_type, parent, parent_type + +""" +Permutation [group ring](https://en.wikipedia.org/wiki/Group_ring) over a base ring, also known as group algebra. + +- `base_ring`: The base ring, whose elements are used as coefficients of permutations. +- `l`: The length of permutations. + +To construct a permutation group ring, use [`PermutationGroupRing`](@ref). + +```jldoctest +julia> ENV["NEMO_PRINT_BANNER"] = "false"; using Nemo: GF, Perm + +julia> R = PermutationGroupRing(GF(2), 3) +Permutation group ring over Prime field of characteristic 2 +``` + +To construct an element of a permutation group ring, use the call syntax of the parent object. + +```jldoctest +julia> f0 = R(0) +Dict{AbstractAlgebra.Perm, Nemo.FqFieldElem}() + +julia> f1 = R(1) +Dict{AbstractAlgebra.Perm{Int64}, Nemo.FqFieldElem}(() => 1) + +julia> f2 = R(Perm([1,3,2])) +Dict{Perm{Int64}, Nemo.FqFieldElem}((2,3) => 1) + +julia> f3 = R(Dict(Perm(3) => 1, Perm([3,1,2]) => 1)) +Dict{Perm{Int64}, Nemo.FqFieldElem}(() => 1, (1,3,2) => 1) +``` + +To perform arithmetic operations, use the standard arithmetic operators. + +```jldoctest +julia> f1 + f2 +Dict{Perm, Nemo.FqFieldElem}(() => 1, (2,3) => 1) + +julia> f2 * f3 +Dict{Perm, Nemo.FqFieldElem}((1,3) => 1, (2,3) => 1) + +julia> f3 * 1 + 1 +Dict{Perm, Nemo.FqFieldElem}((1,3,2) => 1) + +julia> f3' +Dict{Perm, Nemo.FqFieldElem}(() => 1, (1,2,3) => 1) +``` + +See also: [`PermGroupRingElem`](@ref). +""" @attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing base_ring::Ring l::Int @@ -17,6 +67,14 @@ end const PermGroupRingElemID = CacheDictType{Tuple{Ring, Int}, NCRing}() +""" +Element of a [`PermGroupRing`](@ref). + +- `coeffs`: A dictionary of permutations and their coefficients. Empty dictionary represents zero. +- `parent`: The parent group ring in type `PermGroupRing`. + +See also: [`PermGroupRing`](@ref). +""" mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem coeffs::Dict{<:Perm,T} parent::PermGroupRing{T} @@ -63,6 +121,17 @@ iszero(f::PermGroupRingElem) = f == 0 isone(f::PermGroupRingElem) = f == 1 +# String I/O + +function show(io::IO, R::PermGroupRing) + print(io, "Permutation group ring over ") + show(io, base_ring(R)) + end + + function show(io::IO, f::PermGroupRingElem) + print(io, f.coeffs) + end + # Arithmetic functions function -(a::PermGroupRingElem{T}) where {T<:RingElement} @@ -152,7 +221,7 @@ end ==(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a == p # TODO Some functionality are expected by ring interfaces but not necessary for ECC construction, -# including show, hash, exact division, random generation, promotion rules +# including hash, exact division, random generation, promotion rules # Constructors by overloading the call syntax for parent objects @@ -172,7 +241,7 @@ function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:RingElement} return r end -function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:Union{Integer,Rational,AbstractFloat}} +function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{Integer,Rational,AbstractFloat}}) where {T<:RingElement} r = PermGroupRingElem{T}(Dict(k => base_ring(R)(v) for (k, v) in coeffs)) r.parent = R return r @@ -204,10 +273,11 @@ function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} return f end -# TODO We may add more constructors to remove ambiguities - -# Parent constructor +""" +Permutation group ring constructor. +See also: [`PermutationGroupRing`](@ref). +""" function PermutationGroupRing(R::Ring, l::Int, cached::Bool=true) T = elem_type(R) return PermGroupRing{T}(R, l, cached) @@ -223,4 +293,7 @@ function adjoint(a::PermGroupRingElem{T}) where {T<:FqFieldElem} return r end +""" +Construct a cyclic permutation of length `l` with a shift `n`. +""" cyclic_permutation(n::Int, l::Int) = Perm(vcat(n+1:l, 1:n)) From 8998b79fc29d035a20622b4cefd57bbf537d73ba Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 12:49:53 +0800 Subject: [PATCH 22/67] remove parameterization of lifted code types --- src/ecc/codes/classical/lifted.jl | 16 +++++++++++----- src/ecc/codes/lifted_product.jl | 14 +++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 1ada5863a..3f46ac87e 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,11 +1,17 @@ import Nemo: characteristic, lift, matrix_repr -struct LiftedCode{T} <: ClassicalCode where T<:NCRingElement - A::Matrix{T} +""" +Classical codes lifted over a permutation group ring. + +- `A::Matrix`: the base matrix of the code, whose elements are in a permutation group ring. +- `repr::Function`: a function that converts a permutation group ring element to a matrix; default to be `permutation_repr` for GF(2)-algebra. +""" +struct LiftedCode <: ClassicalCode + A::Matrix{PermGroupRingElem} repr::Function - function LiftedCode(A::Matrix{T}, repr::Function) where T<:NCRingElement - new{T}(A, repr) + function LiftedCode(A::Matrix{PermGroupRingElem{T}}, repr::Function) where T + new(A, repr) end end @@ -20,7 +26,7 @@ function permutation_repr(x::PermGroupRingElem{FqFieldElem}) return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end -function lift(repr::Function, mat::Matrix{T}) where T<:NCRingElement +function lift(repr::Function, mat::Matrix{PermGroupRingElem}) vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 3ba0eea31..115e3c667 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,20 +1,20 @@ import Nemo: matrix_space -struct LPCode{T} <: AbstractECC - c₁::LiftedCode{T} - c₂::LiftedCode{T} +struct LPCode <: AbstractECC + c₁::LiftedCode + c₂::LiftedCode repr::Function - function LPCode(c₁::LiftedCode{T}, c₂::LiftedCode{T}, repr::Function) where T - new{T}(c₁, c₂, repr) + function LPCode(c₁::LiftedCode, c₂::LiftedCode, repr::Function) + new(c₁, c₂, repr) end end -function LPCode(c₁::LiftedCode{PermGroupRingElem{FqFieldElem}}, c₂::LiftedCode{PermGroupRingElem{FqFieldElem}}) +function LPCode(c₁::LiftedCode, c₂::LiftedCode) LPCode(c₁, c₂, permutation_repr) end -iscss(::Type{LPCode{T}}) where {T <: NCRingElement} = true +iscss(::Type{LPCode}) = true function parity_checks_xz(c::LPCode) c.c₁.A[1,1].parent == c.c₂.A[1,1].parent || error("The base rings of the two codes must be the same") From abc2c92e5860d5af8b820ce3e4157b6ed5018458 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 13:38:18 +0800 Subject: [PATCH 23/67] export permutation_repr --- src/ecc/ECC.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 8d5bacc7e..1edde4350 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -16,7 +16,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, code_n, code_s, code_k, rate, distance, isdegenerate, faults_matrix, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, - PermGroupRing, PermutationGroupRing, PermGroupRingElem, cyclic_permutation, # group utils + PermGroupRing, PermutationGroupRing, PermGroupRingElem, cyclic_permutation, permutation_repr, # group utils RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, From 87941bbe715a7d6c3f72f70bf37d9a782d567117 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 13:38:57 +0800 Subject: [PATCH 24/67] add docs for lifted and lifted product code --- docs/src/references.bib | 29 +++++++++++++++++++++++++++++ src/ecc/codes/classical/lifted.jl | 14 +++++++++++--- src/ecc/codes/lifted_product.jl | 22 +++++++++++++++++++--- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/docs/src/references.bib b/docs/src/references.bib index 6a50a9194..9593ec285 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -402,3 +402,32 @@ @inproceedings{brown2013short pages = {346--350}, doi = {10.1109/ISIT.2013.6620245} } + +@article{panteleev2021degenerate, + title = {Degenerate {{Quantum LDPC Codes With Good Finite Length Performance}}}, + author = {Panteleev, Pavel and Kalachev, Gleb}, + year = {2021}, + month = nov, + journal = {Quantum}, + volume = {5}, + eprint = {1904.02703}, + primaryclass = {quant-ph}, + pages = {585}, + issn = {2521-327X}, + doi = {10.22331/q-2021-11-22-585}, + archiveprefix = {arXiv} +} + + +@inproceedings{panteleev2022asymptotically, + title = {Asymptotically Good {{Quantum}} and Locally Testable Classical {{LDPC}} Codes}, + booktitle = {Proceedings of the 54th {{Annual ACM SIGACT Symposium}} on {{Theory}} of {{Computing}}}, + author = {Panteleev, Pavel and Kalachev, Gleb}, + year = {2022}, + month = jun, + pages = {375--388}, + publisher = {ACM}, + address = {Rome Italy}, + doi = {10.1145/3519935.3520017}, + isbn = {978-1-4503-9264-8} +} diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 3f46ac87e..451190cae 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,10 +1,15 @@ import Nemo: characteristic, lift, matrix_repr """ -Classical codes lifted over a permutation group ring. +Classical codes lifted over a permutation group ring [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). - `A::Matrix`: the base matrix of the code, whose elements are in a permutation group ring. -- `repr::Function`: a function that converts a permutation group ring element to a matrix; default to be `permutation_repr` for GF(2)-algebra. +- `repr::Function`: a function that converts a permutation group ring element to a matrix; + default to be [`permutation_repr`](@ref) for GF(2)-algebra. + +The parity-check matrix is constructed by applying `repr` to elements of `A`, known as a lift. + +See also: [`LPCode`](@ref), [`PermGroupRing`](@ref). """ struct LiftedCode <: ClassicalCode A::Matrix{PermGroupRingElem} @@ -22,6 +27,9 @@ function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) LiftedCode(A, permutation_repr) end +""" +Represent a permutation group ring element by mapping permutations to circulant matrices. +""" function permutation_repr(x::PermGroupRingElem{FqFieldElem}) return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) end @@ -40,6 +48,6 @@ function mod2rank(h::Matrix{<:Integer}) rank(S(h)) end -code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # note that they are degenagate in general +code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # note that redundant rows exist in general code_k(c::LiftedCode) = code_n(c) - code_s(c) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 115e3c667..09f345fa2 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,5 +1,24 @@ import Nemo: matrix_space + +""" +Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) + +- `c₁::LiftedCode`: the first lifted code +- `c₂::LiftedCode`: the second lifted code +- `repr::Function`: a function that converts a permutation group ring element to a matrix; + default to be [`permutation_repr`](@ref) for GF(2)-algebra. + +A lifted product code is constructed by hypergraph product of the two lifted codes `c₁` and `c₂`. +Here, the hypergraph product is taken over a group ring, which serves as the base ring for both lifted codes. +After the hypergraph product, the parity-check matrices are lifted by `repr`. + +Children of code families: +- Hypergraph product code: use trivial group ring `PermutationGroupRing(GF(2), 1)` in lifted codes. +- Two-block group-algebra (2GBA) code: choose 1×1 base matrices for `c₁` and `c₂`. + +See also: [`LiftedCode`](@ref), [`PermGroupRing`](@ref). +""" struct LPCode <: AbstractECC c₁::LiftedCode c₂::LiftedCode @@ -37,6 +56,3 @@ function code_s(c::LPCode) end code_k(c::LPCode) = code_n(c) - code_s(c) - -# HGPCode = LPCode{Bool} -# or from a trivial group ring, `R = PermutationGroupRing(GF(2), 1)` From 4fd7fc9c111b3fdc52104467fad023fb1b4a6f38 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 13:53:46 +0800 Subject: [PATCH 25/67] update docs for PermGroupRing --- src/ecc/group_ring.jl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 2300b2a52..e207729ea 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -11,18 +11,18 @@ Permutation [group ring](https://en.wikipedia.org/wiki/Group_ring) over a base r - `base_ring`: The base ring, whose elements are used as coefficients of permutations. - `l`: The length of permutations. -To construct a permutation group ring, use [`PermutationGroupRing`](@ref). +Basic usage: + +- To construct a permutation group ring, use [`PermutationGroupRing`](@ref). +- To construct an element of a permutation group ring, use the call syntax of the parent object. +- To perform arithmetic operations, use the standard arithmetic operators. ```jldoctest julia> ENV["NEMO_PRINT_BANNER"] = "false"; using Nemo: GF, Perm julia> R = PermutationGroupRing(GF(2), 3) Permutation group ring over Prime field of characteristic 2 -``` -To construct an element of a permutation group ring, use the call syntax of the parent object. - -```jldoctest julia> f0 = R(0) Dict{AbstractAlgebra.Perm, Nemo.FqFieldElem}() @@ -34,11 +34,7 @@ Dict{Perm{Int64}, Nemo.FqFieldElem}((2,3) => 1) julia> f3 = R(Dict(Perm(3) => 1, Perm([3,1,2]) => 1)) Dict{Perm{Int64}, Nemo.FqFieldElem}(() => 1, (1,3,2) => 1) -``` - -To perform arithmetic operations, use the standard arithmetic operators. -```jldoctest julia> f1 + f2 Dict{Perm, Nemo.FqFieldElem}(() => 1, (2,3) => 1) From ea084812faba92ecd33b2e5e0beabc32d3186ecf Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 16:11:46 +0800 Subject: [PATCH 26/67] fix jldoctest --- src/ecc/group_ring.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index e207729ea..337bf272e 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -24,10 +24,10 @@ julia> R = PermutationGroupRing(GF(2), 3) Permutation group ring over Prime field of characteristic 2 julia> f0 = R(0) -Dict{AbstractAlgebra.Perm, Nemo.FqFieldElem}() +Dict{Perm, Nemo.FqFieldElem}() julia> f1 = R(1) -Dict{AbstractAlgebra.Perm{Int64}, Nemo.FqFieldElem}(() => 1) +Dict{Perm{Int64}, Nemo.FqFieldElem}(() => 1) julia> f2 = R(Perm([1,3,2])) Dict{Perm{Int64}, Nemo.FqFieldElem}((2,3) => 1) @@ -123,7 +123,7 @@ function show(io::IO, R::PermGroupRing) print(io, "Permutation group ring over ") show(io, base_ring(R)) end - + function show(io::IO, f::PermGroupRingElem) print(io, f.coeffs) end From c48f16045992b6a5f14e03b40e508dac12177d7c Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 17:36:39 +0800 Subject: [PATCH 27/67] fix a type ambiguity --- src/ecc/group_ring.jl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 337bf272e..4dfa17cb8 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -227,22 +227,20 @@ function (R::PermGroupRing{T})() where {T<:RingElement} return r end -function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,T}) where {T<:RingElement} - for (k,v) in coeffs - length(k.d) == R.l || error("Invalid permutation length") - parent(v) == R || error("Unable to coerce a group ring element") +function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{T,Integer,Rational,AbstractFloat}}) where {T<:RingElement} + if valtype(coeffs) == T + for (k,v) in coeffs + length(k.d) == R.l || error("Invalid permutation length") + parent(v) == R || error("Unable to coerce a group ring element") + end + else + coeffs = Dict(k => base_ring(R)(v) for (k, v) in coeffs) end r = PermGroupRingElem{T}(coeffs) r.parent = R return r end -function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{Integer,Rational,AbstractFloat}}) where {T<:RingElement} - r = PermGroupRingElem{T}(Dict(k => base_ring(R)(v) for (k, v) in coeffs)) - r.parent = R - return r -end - function (R::PermGroupRing{T})(n::Union{Integer,Rational,AbstractFloat}) where {T<:RingElement} coeffs = iszero(n) ? Dict{Perm,T}() : Dict(Perm(R.l) => base_ring(R)(n)) r = PermGroupRingElem{T}(coeffs) From 53322b587c3e4c94175b42c38578cbb2b429e697 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 10 Jul 2024 17:42:56 +0800 Subject: [PATCH 28/67] add nsample for small LP codes --- test/test_ecc_decoder_all_setups.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 6bfe90f0a..4c540d33a 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -101,13 +101,13 @@ push!(other_lifted_product_codes, LPCode(LiftedCode(A), LiftedCode(B))) for c in codes for s in setups for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] - nsamples = code_n(c)>400 ? 1000 : 10000 + nsamples = code_n(c)>400 ? 1000 : 100000 # take fewer samples for larger codes to save time e = evaluate_decoder(d(c), s, nsamples) # @show c # @show s # @show e - @assert max(e...) < noise/4 + @assert max(e...) < noise/4 (c, s, e) end end end From 75c257199b0fca18a3d8bdc8882d11030260f802 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 31 Jul 2024 17:41:39 +0800 Subject: [PATCH 29/67] update lifted code for clarity --- src/ecc/codes/classical/lifted.jl | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 451190cae..c78568dca 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -7,7 +7,8 @@ Classical codes lifted over a permutation group ring [panteleev2021degenerate](@ - `repr::Function`: a function that converts a permutation group ring element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. -The parity-check matrix is constructed by applying `repr` to elements of `A`, known as a lift. +The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. +This will enlarge the parity check matrix from `A` with each element being inflated into a matrix. The procedure is called a lift [panteleev2022asymptotically](@cite). See also: [`LPCode`](@ref), [`PermGroupRing`](@ref). """ @@ -19,10 +20,7 @@ struct LiftedCode <: ClassicalCode new(A, repr) end end - function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) - # TODO we may also want to handle the case where the base ring is not GF(2), - # such as residue_ring(ZZ, 2), which is mathematically equivalent, and residue_ring(ZZ, n) for n > 2 !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") LiftedCode(A, permutation_repr) end @@ -31,14 +29,24 @@ end Represent a permutation group ring element by mapping permutations to circulant matrices. """ function permutation_repr(x::PermGroupRingElem{FqFieldElem}) - return sum([Int(lift(ZZ, x.coeffs[k])) .* Array(matrix_repr(k)) for k in keys(x.coeffs)], init=zeros(Bool, parent(x).l, parent(x).l)) + mat = zeros(Bool, parent(x).l, parent(x).l) + for k in keys(x.coeffs) + c = Int(lift(ZZ, x.coeffs[k])) # the coefficient of the group element `k`, which is converted to Int type for matrix calculation + mat += c .* Array(matrix_repr(k)) # the coefficient times the matrix representation of the corresponding group element + end + return mat end function lift(repr::Function, mat::Matrix{PermGroupRingElem}) vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end -parity_checks(c::LiftedCode) = lift(c.repr, c.A) +function parity_checks(c::LiftedCode) + h = lift(c.repr, c.A) + rk = mod2rank(h) # TODO mod2rank and canonicalize, which is more efficient? + rk < size(h, 1) && @warn "The lifted code has redundant rows" + return h +end code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) @@ -48,6 +56,6 @@ function mod2rank(h::Matrix{<:Integer}) rank(S(h)) end -code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # note that redundant rows exist in general +code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # TODO move this mod2rank to a common place code_k(c::LiftedCode) = code_n(c) - code_s(c) From 87ce167530ddd1a2bf70f13577d83060a2bb9047 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Wed, 31 Jul 2024 17:47:52 +0800 Subject: [PATCH 30/67] fix decoder pipeline for cases with redundant parity checks; thereby, enable syndrome ECC setups for BP Note that `vect` is replaced with `vcat` to avoid `promote_typeof` stackover flow, as reported also in https://github.com/JuliaLang/julia/issues/45454 --- src/ecc/ECC.jl | 5 ++++- src/ecc/decoder_pipeline.jl | 8 ++++---- test/test_ecc_decoder_all_setups.jl | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 1edde4350..d4ff87c6c 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -89,7 +89,10 @@ code_n(s::Stabilizer) = nqubits(s) function code_s end code_s(c::AbstractECC) = code_s(parity_checks(c)) -code_s(s::Stabilizer) = length(s) +function code_s(s::Stabilizer) + _, _, r = canonicalize!(Base.copy(s), ranks=true) + return r +end """The number of logical qubits in a code.""" code_k(c) = code_n(c) - code_s(c) diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index d1fbe897f..902f54c0e 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -101,7 +101,7 @@ function physical_ECC_circuit(H, setup::NaiveSyndromeECCSetup) end mem_error_circ = [PauliError(i, setup.mem_noise) for i in 1:nqubits(H)] - circ = [mem_error_circ..., noisy_syndrome_circ...] + circ = vcat(mem_error_circ, noisy_syndrome_circ) return circ, syndrome_bits, n_anc end @@ -119,7 +119,7 @@ function physical_ECC_circuit(H, setup::ShorSyndromeECCSetup) mem_error_circ = [PauliError(i, setup.mem_noise) for i in 1:nqubits(H)] - circ = [prep_anc..., mem_error_circ..., noisy_syndrome_circ...] + circ = vcat(prep_anc, mem_error_circ, noisy_syndrome_circ) circ, syndrome_bits, n_anc end @@ -141,12 +141,12 @@ function evaluate_decoder(d::AbstractSyndromeDecoder, setup::AbstractECCSetup, n # Evaluate the probability for X logical error (the Z-observable part of the faults matrix is used) X_error = evaluate_decoder( d, nsamples, - [encoding_circ..., physical_noisy_circ..., logZ_circ...], + vcat(encoding_circ, physical_noisy_circ, logZ_circ), syndrome_bits, logZ_bits, O[end÷2+1:end,:]) # Evaluate the probability for Z logical error (the X-observable part of the faults matrix is used) Z_error = evaluate_decoder( d, nsamples, - [preX..., encoding_circ..., physical_noisy_circ..., logX_circ...], + vcat(preX, encoding_circ, physical_noisy_circ, logX_circ), syndrome_bits, logX_bits, O[1:end÷2,:]) return (X_error, Z_error) end diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 4c540d33a..4650cbb98 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -93,8 +93,8 @@ push!(other_lifted_product_codes, LPCode(LiftedCode(A), LiftedCode(B))) setups = [ CommutationCheckECCSetup(noise), - # NaiveSyndromeECCSetup(noise, 0), - # ShorSyndromeECCSetup(noise, 0), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), ] # lifted product codes currently trigger errors in syndrome circuits From 3279381495d2dfa0a9f0b6a2b19696e192901244 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Thu, 1 Aug 2024 10:17:41 +0800 Subject: [PATCH 31/67] add power operation --- src/ecc/group_ring.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 4dfa17cb8..5bfe150aa 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -1,4 +1,4 @@ -import Base: *, +, -, ==, deepcopy_internal, show, isone, iszero, one, zero, adjoint +import Base: *, +, -, ^, ==, deepcopy_internal, show, isone, iszero, one, zero, adjoint import Nemo: Ring, RingElem, RingElement, NCRing, NCRingElem, NCRingElement, Perm, CacheDictType, @attributes, base_ring, base_ring_type, elem_type, get_cached!, @@ -50,7 +50,7 @@ Dict{Perm, Nemo.FqFieldElem}(() => 1, (1,2,3) => 1) See also: [`PermGroupRingElem`](@ref). """ -@attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing +@attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing # TODO if this is only used for ECC, maybe we should consider fixing the base ring to be Z2 base_ring::Ring l::Int @@ -174,6 +174,18 @@ function *(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingEleme return r end +function ^(a::PermGroupRingElem{T}, n::Int) where {T<:RingElement} + if n == 0 + return one(parent(a)) + elseif n == 1 + return a + elseif n < 0 + DomainError(n) + end + r = *(repeat([a], n)...) + return r +end + # Ad hoc arithmetic functions *(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = a * parent(a)(n) From d43e582cd629481d8b3a3ec02213f2d3f174ad8a Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Thu, 1 Aug 2024 10:19:00 +0800 Subject: [PATCH 32/67] add new constructors and functions and refactor lifted product --- src/ecc/ECC.jl | 2 +- src/ecc/codes/classical/lifted.jl | 17 ++++++- src/ecc/codes/lifted_product.jl | 73 +++++++++++++++++++++++------ test/test_ecc_decoder_all_setups.jl | 40 ++++++---------- 4 files changed, 89 insertions(+), 43 deletions(-) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index d4ff87c6c..05a5cd06e 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -21,7 +21,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, Toric, Gottesman, Surface, Concat, CircuitCode, - LPCode, + LPCode, two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index c78568dca..57394aa7d 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -20,11 +20,24 @@ struct LiftedCode <: ClassicalCode new(A, repr) end end + function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") LiftedCode(A, permutation_repr) end +function LiftedCode(perm_array::Matrix{<:Perm}) + l = length(perm_array[1,1].d) + R = PermutationGroupRing(GF(2), l) + A = Matrix(matrix_space(R, size(perm_array)...)(perm_array)) # convert perms to group ring elements + LiftedCode(A) +end + +function LiftedCode(shift_array::Matrix{Int}, l::Int) + perm_array = map(n->cyclic_permutation(n, l), shift_array) + LiftedCode(perm_array) +end + """ Represent a permutation group ring element by mapping permutations to circulant matrices. """ @@ -50,12 +63,12 @@ end code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) -function mod2rank(h::Matrix{<:Integer}) +function mod2rank(h::Matrix{<:Integer}) # TODO move this mod2rank to a common place Z2, _ = residue_ring(ZZ, 2) S = matrix_space(Z2, size(h)...) rank(S(h)) end -code_s(c::LiftedCode) = mod2rank(parity_checks(c)) # TODO move this mod2rank to a common place +code_s(c::LiftedCode) = mod2rank(parity_checks(c)) code_k(c::LiftedCode) = code_n(c) - code_s(c) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 09f345fa2..786b0b5ab 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -4,40 +4,56 @@ import Nemo: matrix_space """ Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) -- `c₁::LiftedCode`: the first lifted code -- `c₂::LiftedCode`: the second lifted code -- `repr::Function`: a function that converts a permutation group ring element to a matrix; +- `A::Matrix{PermGroupRingElem}`: the first base matrix for constructing the lifted product code, whose elements are in a permutation group ring; +- `B::Matrix{PermGroupRingElem}`: the second base matrix for constructing the lifted product code, whose elements are in the same permutation group ring as `A`; +- `repr::Function`: a function that converts the permutation group ring element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. A lifted product code is constructed by hypergraph product of the two lifted codes `c₁` and `c₂`. Here, the hypergraph product is taken over a group ring, which serves as the base ring for both lifted codes. After the hypergraph product, the parity-check matrices are lifted by `repr`. - -Children of code families: -- Hypergraph product code: use trivial group ring `PermutationGroupRing(GF(2), 1)` in lifted codes. -- Two-block group-algebra (2GBA) code: choose 1×1 base matrices for `c₁` and `c₂`. +The lifting is achieved by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from a group algebra element to a binary matrix. See also: [`LiftedCode`](@ref), [`PermGroupRing`](@ref). """ struct LPCode <: AbstractECC - c₁::LiftedCode - c₂::LiftedCode + A::Matrix{PermGroupRingElem} + B::Matrix{PermGroupRingElem} repr::Function + function LPCode(A::Matrix{PermGroupRingElem{T}}, B::Matrix{PermGroupRingElem{T}}, repr::Function) where T + A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") + new(A, B, repr) + end + function LPCode(c₁::LiftedCode, c₂::LiftedCode, repr::Function) - new(c₁, c₂, repr) + c₁.A[1, 1].parent == c₂.A[1, 1].parent || error("The base rings of the two codes must be the same") + new(c₁.A, c₂.A, repr) end end +function LPCode(A::Matrix{PermGroupRingElem{T}}, B::Matrix{PermGroupRingElem{T}}) where T + A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") + LPCode(A, B, permutation_repr) +end + function LPCode(c₁::LiftedCode, c₂::LiftedCode) - LPCode(c₁, c₂, permutation_repr) + c₁.A[1, 1].parent == c₂.A[1, 1].parent || error("The base rings of the two codes must be the same") + LPCode(c₁, c₂, c₁.repr) # use the same `repr` as the first code +end + +function LPCode(perm_array1::Matrix{<:Perm}, perm_array2::Matrix{<:Perm}) + LPCode(LiftedCode(perm_array1), LiftedCode(perm_array2)) +end + +function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int) + LPCode(LiftedCode(shift_array1, l), LiftedCode(shift_array2, l)) end iscss(::Type{LPCode}) = true function parity_checks_xz(c::LPCode) - c.c₁.A[1,1].parent == c.c₂.A[1,1].parent || error("The base rings of the two codes must be the same") - hx, hz = hgp(c.c₁.A, c.c₂.A) + hx, hz = hgp(c.A, c.B) hx, hz = lift(c.repr, hx), lift(c.repr, hz) return hx, hz end @@ -48,7 +64,7 @@ parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) -code_n(c::LPCode) = size(c.repr(parent(c.c₁.A[1, 1])(0)), 2) * (size(c.c₁.A, 2) * size(c.c₂.A, 2) + size(c.c₁.A, 1) * size(c.c₂.A, 1)) +code_n(c::LPCode) = size(c.repr(parent(c.A[1, 1])(0)), 2) * (size(c.A, 2) * size(c.B, 2) + size(c.A, 1) * size(c.B, 1)) function code_s(c::LPCode) hx, hz = parity_checks_xz(c) @@ -56,3 +72,32 @@ function code_s(c::LPCode) end code_k(c::LPCode) = code_n(c) - code_s(c) + + +""" +Two-block group algebra (2GBA) codes. +""" +function two_block_group_algebra_codes(a::PermGroupRingElem, b::PermGroupRingElem) + A = reshape([a], (1, 1)) + B = reshape([b], (1, 1)) + LPCode(A, B) +end + +""" +Generalized bicycle codes. +""" +function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) + R = PermutationGroupRing(GF(2), l) + a = sum(R(cyclic_permutation(n, l)) for n in a_shifts) + b = sum(R(cyclic_permutation(n, l)) for n in b_shifts) + two_block_group_algebra_codes(a, b) +end + +""" +Bicycle codes. +""" +function bicycle_codes(a_shifts::Array{Int}, l::Int) + R = PermutationGroupRing(GF(2), l) + a = sum(R(cyclic_permutation(n, l)) for n in a_shifts) + two_block_group_algebra_codes(a, a') +end diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 4650cbb98..f3d16b5d2 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -39,7 +39,7 @@ end # test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 B04 = Dict( - 7 => [ 0 0 0 0; 0 1 2 5; 0 6 3 1], + 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], 17 => [0 0 0 0; 0 1 2 11; 0 8 12 13], 19 => [0 0 0 0; 0 2 6 9; 0 16 7 11] @@ -51,21 +51,10 @@ B118 = Dict( 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], ) -LP04 = [] -for l in keys(B04) - A = map(B04[l]) do x - (PermutationGroupRing(GF(2), l))(cyclic_permutation(x, l)) - end - push!(LP04, LPCode(LiftedCode(A), LiftedCode(A))) -end +LP04 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B04] +LP118 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B118] -LP118 = [] -for l in keys(B118) - A = map(B118[l]) do x - (PermutationGroupRing(GF(2), l))(cyclic_permutation(x, l)) - end - push!(LP118, LPCode(LiftedCode(A), LiftedCode(A))) -end +# TODO add generalized bicycle codes, after which maybe we should remove some of the above codes other_lifted_product_codes = [] @@ -73,18 +62,17 @@ other_lifted_product_codes = [] l = 63 R = PermutationGroupRing(GF(2), l) -A = zeros(R, 7,7) -A[LinearAlgebra.diagind(A)] .= R(cyclic_permutation(27, l)) -A[LinearAlgebra.diagind(A, -1)] .= R(cyclic_permutation(54, l)) -A[LinearAlgebra.diagind(A, 6)] .= R(cyclic_permutation(54, l)) +A = zeros(R, 7, 7) +x = R(cyclic_permutation(1, l)) +A[LinearAlgebra.diagind(A)] .= x^27 +A[LinearAlgebra.diagind(A, -1)] .= x^54 +A[LinearAlgebra.diagind(A, 6)] .= x^54 A[LinearAlgebra.diagind(A, -2)] .= R(1) A[LinearAlgebra.diagind(A, 5)] .= R(1) -B = zeros(R, 1, 1) -B[1,1] = (R(1) + R(cyclic_permutation(1, l)) + R(cyclic_permutation(6, l)))' - -push!(other_lifted_product_codes, LPCode(LiftedCode(A), LiftedCode(B))) +B = reshape([(1 + x + x^6)'], (1, 1)) +push!(other_lifted_product_codes, LPCode(A, B)) @testset "belief prop decoders, good for sparse codes" begin codes = vcat(LP04, LP118, other_lifted_product_codes) @@ -100,14 +88,14 @@ push!(other_lifted_product_codes, LPCode(LiftedCode(A), LiftedCode(B))) for c in codes for s in setups - for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] - nsamples = code_n(c)>400 ? 1000 : 100000 + for d in [c -> PyBeliefPropOSDecoder(c, maxiter=10)] + nsamples = code_n(c) > 400 ? 1000 : 100000 # take fewer samples for larger codes to save time e = evaluate_decoder(d(c), s, nsamples) # @show c # @show s # @show e - @assert max(e...) < noise/4 (c, s, e) + @assert max(e...) < noise / 4 (c, s, e) end end end From 2707d9b0a92099986b613a4efef6401cef911020 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Thu, 1 Aug 2024 12:48:25 +0800 Subject: [PATCH 33/67] fix `code_s` and types in lifted --- src/ecc/ECC.jl | 19 ++++++++++++++----- src/ecc/codes/classical/lifted.jl | 2 +- src/ecc/codes/lifted_product.jl | 12 +++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 05a5cd06e..6c6b2aca5 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -87,15 +87,24 @@ code_n(s::Stabilizer) = nqubits(s) """The number of stabilizer checks in a code.""" function code_s end - +code_s(s::Stabilizer) = length(s) code_s(c::AbstractECC) = code_s(parity_checks(c)) -function code_s(s::Stabilizer) +# function code_s(s::Stabilizer) +# _, _, r = canonicalize!(Base.copy(s), ranks=true) +# return r +# end + +""" +The number of logical qubits in a code. + +Note that when redundant rows exist in the parity check matrix, the number of logical qubits `code_k(c)` will be greater than `code_n(c) - code_s(c)`, where the difference equals the redundancy. +""" +function code_k(s::Stabilizer) _, _, r = canonicalize!(Base.copy(s), ranks=true) - return r + return code_n(s) - r end -"""The number of logical qubits in a code.""" -code_k(c) = code_n(c) - code_s(c) +code_k(c::AbstractECC) = code_k(parity_checks(c)) """The rate of a code.""" function rate(c) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 57394aa7d..5aaa2cd09 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -16,7 +16,7 @@ struct LiftedCode <: ClassicalCode A::Matrix{PermGroupRingElem} repr::Function - function LiftedCode(A::Matrix{PermGroupRingElem{T}}, repr::Function) where T + function LiftedCode(A::Matrix{<: PermGroupRingElem}, repr::Function) new(A, repr) end end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 786b0b5ab..3dce4de2c 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -21,7 +21,7 @@ struct LPCode <: AbstractECC B::Matrix{PermGroupRingElem} repr::Function - function LPCode(A::Matrix{PermGroupRingElem{T}}, B::Matrix{PermGroupRingElem{T}}, repr::Function) where T + function LPCode(A::Matrix{<: PermGroupRingElem}, B::Matrix{<: PermGroupRingElem}, repr::Function) A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") new(A, B, repr) end @@ -32,7 +32,7 @@ struct LPCode <: AbstractECC end end -function LPCode(A::Matrix{PermGroupRingElem{T}}, B::Matrix{PermGroupRingElem{T}}) where T +function LPCode(A::Matrix{<: PermGroupRingElem}, B::Matrix{<: PermGroupRingElem}) A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") LPCode(A, B, permutation_repr) end @@ -66,13 +66,7 @@ parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) code_n(c::LPCode) = size(c.repr(parent(c.A[1, 1])(0)), 2) * (size(c.A, 2) * size(c.B, 2) + size(c.A, 1) * size(c.B, 1)) -function code_s(c::LPCode) - hx, hz = parity_checks_xz(c) - mod2rank(hx) + mod2rank(hz) -end - -code_k(c::LPCode) = code_n(c) - code_s(c) - +code_s(c::LPCode) = size(c.repr(parent(c.A[1, 1])(0)), 1) * (size(c.A, 1) * size(c.B, 2) + size(c.A, 2) * size(c.B, 1)) """ Two-block group algebra (2GBA) codes. From 6fb093cd30c886c77d681bbd15c8f3cc391479f8 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Thu, 1 Aug 2024 12:51:25 +0800 Subject: [PATCH 34/67] move `LPCode` examples to base; (partially) fix naive syndrome circuits tests LP118 codes still fail in "zero syndrome for logical states" when `mctrajectory!` is used --- test/test_ecc.jl | 4 ++-- test/test_ecc_base.jl | 20 +++++++++++++++++++- test/test_ecc_decoder_all_setups.jl | 18 ------------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/test_ecc.jl b/test/test_ecc.jl index 25b9fe4e0..08ffc6089 100644 --- a/test/test_ecc.jl +++ b/test/test_ecc.jl @@ -10,7 +10,7 @@ codes = all_testablable_code_instances() function test_naive_syndrome(c::AbstractECC, e::Bool) # create a random logical state unencoded_qubits = random_stabilizer(code_k(c)) - bufferqubits = one(Stabilizer,code_s(c)) + bufferqubits = one(Stabilizer, code_n(c) - code_k(c)) logicalqubits = bufferqubits⊗unencoded_qubits mctrajectory!(logicalqubits, naive_encoding_circuit(c)) if e @@ -51,7 +51,7 @@ function test_with_pframes(code) ancqubits = code_s(code) regbits = ancqubits frames = PauliFrame(nframes, dataqubits+ancqubits, regbits) - circuit = [ecirc..., scirc...] + circuit = vcat(ecirc, scirc) pftrajectories(frames, circuit) @test sum(pfmeasurements(frames)) == 0 end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 9b4dac227..6db5ed6cc 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -14,13 +14,31 @@ random_circuit_code_args = vcat( [map(f -> getfield(random_all_to_all_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_all_to_all_circuit_args] ) +# test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 +B04 = Dict( + 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], + 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], + 17 => [0 0 0 0; 0 1 2 11; 0 8 12 13], + 19 => [0 0 0 0; 0 2 6 9; 0 16 7 11] +) + +B118 = Dict( + 16 => [0 0 0 0 0; 0 2 4 7 11; 0 3 10 14 15], + 21 => [0 0 0 0 0; 0 4 5 7 17; 0 14 18 12 11], + 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], +) + +LP04 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B04] +LP118 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B118] + const code_instance_args = Dict( Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], Gottesman => [3, 4, 5], CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], - CircuitCode => random_circuit_code_args + CircuitCode => random_circuit_code_args, + LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118)) ) function all_testablable_code_instances(;maxn=nothing) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index f3d16b5d2..177f1a6ac 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -36,24 +36,6 @@ include("test_ecc_base.jl") end end -# test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 - -B04 = Dict( - 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], - 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], - 17 => [0 0 0 0; 0 1 2 11; 0 8 12 13], - 19 => [0 0 0 0; 0 2 6 9; 0 16 7 11] -) - -B118 = Dict( - 16 => [0 0 0 0 0; 0 2 4 7 11; 0 3 10 14 15], - 21 => [0 0 0 0 0; 0 4 5 7 17; 0 14 18 12 11], - 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], -) - -LP04 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B04] -LP118 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B118] - # TODO add generalized bicycle codes, after which maybe we should remove some of the above codes other_lifted_product_codes = [] From b8d18ed2d8a8c26fda7fee7f115e6c3a7e34917a Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Thu, 1 Aug 2024 13:02:01 +0800 Subject: [PATCH 35/67] weaken code property checks by allowing redundant rows --- src/QuantumClifford.jl | 4 +++- test/test_ecc_codeproperties.jl | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 44fb23312..40796dbac 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -1081,7 +1081,9 @@ end Check basic consistency requirements of a stabilizer. Used in tests. """ function stab_looks_good(s) - c = tab(canonicalize!(copy(s))) + # first remove redundant rows + _, _, rank = canonicalize!(copy(s), ranks=true) + c = tab(s[1:rank]) nrows, ncols = size(c) all((c.phases .== 0x0) .| (c.phases .== 0x2)) || return false H = stab_to_gf2(c) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index c464ea545..b43b30a10 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -31,10 +31,8 @@ end H = parity_checks(code) @test nqubits(code) == size(H, 2) == code_n(code) @test size(H, 1) == code_s(code) - @test code_s(code) + code_k(code) == code_n(code) + @test code_s(code) + code_k(code) >= code_n(code) # possbly exist redundant checks @test size(H, 1) < size(H, 2) - _, _, rank = canonicalize!(copy(H), ranks=true) - @test rank == size(H, 1) # TODO maybe weaken this if we want to permit codes with redundancies @test QuantumClifford.stab_looks_good(copy(H)) end end From 6216fc064e3ad52cc9d0755a890e4040a6563407 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 4 Aug 2024 16:31:22 +0800 Subject: [PATCH 36/67] fix pframe test --- test/test_ecc.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ecc.jl b/test/test_ecc.jl index 64531986d..1417bbe26 100644 --- a/test/test_ecc.jl +++ b/test/test_ecc.jl @@ -50,7 +50,7 @@ ancqubits = code_s(code) regbits = ancqubits frames = PauliFrame(nframes, dataqubits+ancqubits, regbits) - circuit = [ecirc..., scirc...] + circuit = vcat(ecirc, scirc) pftrajectories(frames, circuit) @test sum(pfmeasurements(frames)) == 0 end From c5c52b26733466ccc2dcefd5493ecc7eb30afedf Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Sun, 4 Aug 2024 17:15:31 +0800 Subject: [PATCH 37/67] fix encoding and syndrome circuit tests --- test/test_ecc.jl | 2 +- test/test_ecc_encoding.jl | 2 +- test/test_ecc_syndromes.jl | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_ecc.jl b/test/test_ecc.jl index 1417bbe26..075d27ac5 100644 --- a/test/test_ecc.jl +++ b/test/test_ecc.jl @@ -9,7 +9,7 @@ function test_naive_syndrome(c::AbstractECC, e::Bool) # create a random logical state unencoded_qubits = random_stabilizer(code_k(c)) - bufferqubits = one(Stabilizer,code_s(c)) + bufferqubits = one(Stabilizer, code_n(c) - code_k(c)) logicalqubits = bufferqubits⊗unencoded_qubits mctrajectory!(logicalqubits, naive_encoding_circuit(c)) if e diff --git a/test/test_ecc_encoding.jl b/test/test_ecc_encoding.jl index cab0562ff..78312c57d 100644 --- a/test/test_ecc_encoding.jl +++ b/test/test_ecc_encoding.jl @@ -31,7 +31,7 @@ pre_encₖ = one(Stabilizer, code_k(code)) # n-k ancillary qubits in state zero prepended - pre_encₙ = one(Stabilizer, code_s(code)) ⊗ pre_encₖ + pre_encₙ = one(Stabilizer, code_n(code) - code_k(code)) ⊗ pre_encₖ # running the encoding circuit encodedₙ = mctrajectory!(pre_encₙ, circ)[1] |> canonicalize! diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index 45f4e0f89..0f6f67ea0 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -17,8 +17,8 @@ # no noise naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) - naive_circuit = [ecirc..., naive_scirc...] - shor_circuit = [ecirc..., shor_cat_scirc..., shor_scirc...] + naive_circuit = vcat(ecirc, naive_scirc) + shor_circuit = vcat(ecirc, shor_cat_scirc, shor_scirc) pftrajectories(naive_frames, naive_circuit) pftrajectories(shor_frames, shor_circuit) @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] @@ -27,7 +27,7 @@ naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) pftrajectories(naive_frames, ecirc) - pftrajectories(shor_frames, [ecirc..., shor_cat_scirc...]) + pftrajectories(shor_frames, vcat(ecirc, shor_cat_scirc)) # manually injecting the same type of noise in the frames -- not really a user accessible API p = random_pauli(dataqubits, realphase=true) pₙ = embed(naive_qubits, 1:dataqubits, p) From 029b0a5dc8fbcce02bf8546bc3c7e293b4277183 Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Tue, 27 Aug 2024 11:36:32 +0800 Subject: [PATCH 38/67] update types and tests --- src/ecc/codes/classical/lifted.jl | 6 ++--- src/ecc/codes/lifted_product.jl | 13 +++++----- src/ecc/group_ring.jl | 4 ++++ test/test_ecc_base.jl | 37 ++++++++++++++++++++++++++--- test/test_ecc_decoder_all_setups.jl | 25 +------------------ 5 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 5aaa2cd09..04553ad39 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -13,7 +13,7 @@ This will enlarge the parity check matrix from `A` with each element being infla See also: [`LPCode`](@ref), [`PermGroupRing`](@ref). """ struct LiftedCode <: ClassicalCode - A::Matrix{PermGroupRingElem} + A::PermGroupRingMatrix repr::Function function LiftedCode(A::Matrix{<: PermGroupRingElem}, repr::Function) @@ -21,7 +21,7 @@ struct LiftedCode <: ClassicalCode end end -function LiftedCode(A::Matrix{PermGroupRingElem{FqFieldElem}}) +function LiftedCode(A::PermGroupRingMatrix) !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") LiftedCode(A, permutation_repr) end @@ -50,7 +50,7 @@ function permutation_repr(x::PermGroupRingElem{FqFieldElem}) return mat end -function lift(repr::Function, mat::Matrix{PermGroupRingElem}) +function lift(repr::Function, mat::PermGroupRingMatrix) vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 3dce4de2c..326757b12 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,5 +1,5 @@ import Nemo: matrix_space - +import LinearAlgebra """ Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) @@ -16,12 +16,13 @@ The lifting is achieved by applying `repr` to each element of the matrix resulte See also: [`LiftedCode`](@ref), [`PermGroupRing`](@ref). """ + struct LPCode <: AbstractECC - A::Matrix{PermGroupRingElem} - B::Matrix{PermGroupRingElem} + A::PermGroupRingMatrix + B::PermGroupRingMatrix repr::Function - function LPCode(A::Matrix{<: PermGroupRingElem}, B::Matrix{<: PermGroupRingElem}, repr::Function) + function LPCode(A::PermGroupRingMatrix, B::PermGroupRingMatrix, repr::Function) A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") new(A, B, repr) end @@ -32,7 +33,7 @@ struct LPCode <: AbstractECC end end -function LPCode(A::Matrix{<: PermGroupRingElem}, B::Matrix{<: PermGroupRingElem}) +function LPCode(A::PermGroupRingMatrix, B::PermGroupRingMatrix) A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") LPCode(A, B, permutation_repr) end @@ -53,7 +54,7 @@ end iscss(::Type{LPCode}) = true function parity_checks_xz(c::LPCode) - hx, hz = hgp(c.A, c.B) + hx, hz = hgp(c.A, c.B') hx, hz = lift(c.repr, hx), lift(c.repr, hz) return hx, hz end diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl index 5bfe150aa..1e23302fc 100644 --- a/src/ecc/group_ring.jl +++ b/src/ecc/group_ring.jl @@ -303,3 +303,7 @@ end Construct a cyclic permutation of length `l` with a shift `n`. """ cyclic_permutation(n::Int, l::Int) = Perm(vcat(n+1:l, 1:n)) + +PermGroupRingMatrix = Union{ + Matrix{<:PermGroupRingElem}, + LinearAlgebra.Adjoint{<:PermGroupRingElem,<:Matrix{<:PermGroupRingElem}}} diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 6db5ed6cc..e2595c0be 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -3,6 +3,9 @@ using QuantumClifford using QuantumClifford.ECC using InteractiveUtils +import Nemo: GF +import LinearAlgebra + # generate instances of all implemented codes to make sure nothing skips being checked # We do not include smaller random circuit code because some of them has a bad distance and fails the TableDecoder test @@ -28,8 +31,36 @@ B118 = Dict( 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], ) -LP04 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B04] -LP118 = [LPCode(base_matrix, base_matrix, l) for (l, base_matrix) in B118] +LP04 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B04] +LP118 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B118] +# benchmarking wiki + +# generalized bicyle codes + +test_gb_codes = [ + generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127), + generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), +] + +other_lifted_product_codes = [] + +# from https://arxiv.org/abs/2202.01702v3 +l = 63 +R = PermutationGroupRing(GF(2), l) +A = zeros(R, 7, 7) +x = R(cyclic_permutation(1, l)) +A[LinearAlgebra.diagind(A)] .= x^27 +A[LinearAlgebra.diagind(A, -1)] .= x^54 +A[LinearAlgebra.diagind(A, 6)] .= x^54 +A[LinearAlgebra.diagind(A, -2)] .= R(1) +A[LinearAlgebra.diagind(A, 5)] .= R(1) + +B = reshape([1 + x + x^6], (1, 1)) + +# x^63 == 1 +# how is this not polynomial... + +push!(other_lifted_product_codes, LPCode(A, B)) const code_instance_args = Dict( Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], @@ -38,7 +69,7 @@ const code_instance_args = Dict( CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], CircuitCode => random_circuit_code_args, - LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118)) + LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)) ) function all_testablable_code_instances(;maxn=nothing) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 7bc505ef2..d8e8645af 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -4,9 +4,6 @@ import PyQDecoders import LDPCDecoders - import Nemo: GF - import LinearAlgebra - include("test_ecc_base.jl") @testset "table decoder, good for small codes" begin @@ -100,28 +97,8 @@ end end -# TODO add generalized bicycle codes, after which maybe we should remove some of the above codes - -other_lifted_product_codes = [] - -# from https://arxiv.org/abs/2202.01702v3 - -l = 63 -R = PermutationGroupRing(GF(2), l) -A = zeros(R, 7, 7) -x = R(cyclic_permutation(1, l)) -A[LinearAlgebra.diagind(A)] .= x^27 -A[LinearAlgebra.diagind(A, -1)] .= x^54 -A[LinearAlgebra.diagind(A, 6)] .= x^54 -A[LinearAlgebra.diagind(A, -2)] .= R(1) -A[LinearAlgebra.diagind(A, 5)] .= R(1) - -B = reshape([(1 + x + x^6)'], (1, 1)) - -push!(other_lifted_product_codes, LPCode(A, B)) - @testset "belief prop decoders, good for sparse codes" begin - codes = vcat(LP04, LP118, other_lifted_product_codes) + codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) noise = 0.001 From f18df82954ad5cc99f815b76e54aa4b0589f579d Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Mon, 9 Sep 2024 19:36:05 +0900 Subject: [PATCH 39/67] Replace the group ring with Hecke's GroupAlgebra; also some fixup on code_n, code_k and relaxing code tableau consistency tests --- Project.toml | 2 + src/ecc/ECC.jl | 2 - src/ecc/codes/classical/lifted.jl | 68 ++++--- src/ecc/codes/lifted_product.jl | 57 +++--- src/ecc/codes/util.jl | 48 +++++ src/ecc/group_ring.jl | 309 ------------------------------ test/test_ecc_base.jl | 15 +- test/test_ecc_codeproperties.jl | 2 +- 8 files changed, 121 insertions(+), 382 deletions(-) delete mode 100644 src/ecc/group_ring.jl diff --git a/Project.toml b/Project.toml index 550c83850..992c1bea9 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,12 @@ authors = ["Stefan Krastanov and QuantumSavory community version = "0.9.8" [deps] +AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" HostCPUFeatures = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" ILog2 = "2cd5bd5f-40a1-5050-9e10-fc8cdb6109f5" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 6c6b2aca5..fc6adbf68 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -16,7 +16,6 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, code_n, code_s, code_k, rate, distance, isdegenerate, faults_matrix, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, - PermGroupRing, PermutationGroupRing, PermGroupRingElem, cyclic_permutation, permutation_repr, # group utils RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, @@ -361,7 +360,6 @@ end include("circuits.jl") include("decoder_pipeline.jl") -include("group_ring.jl") include("codes/util.jl") include("codes/classical_codes.jl") diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 04553ad39..0c2e357f3 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,56 +1,64 @@ import Nemo: characteristic, lift, matrix_repr +import Hecke: representation_matrix, GroupAlgebra, GroupAlgebraElem, GroupElem, abelian_group, group_algebra + """ -Classical codes lifted over a permutation group ring [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). +Classical codes lifted over a group algebra [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). + +- `A::Matrix`: the base matrix of the code, whose elements are in a group algebra. +- `repr::Function`: a function that converts a group algebra element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. -- `A::Matrix`: the base matrix of the code, whose elements are in a permutation group ring. -- `repr::Function`: a function that converts a permutation group ring element to a matrix; - default to be [`permutation_repr`](@ref) for GF(2)-algebra. +TODO why we need such a freedom of representation? The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. This will enlarge the parity check matrix from `A` with each element being inflated into a matrix. The procedure is called a lift [panteleev2022asymptotically](@cite). -See also: [`LPCode`](@ref), [`PermGroupRing`](@ref). +See also: [`LPCode`](@ref). """ struct LiftedCode <: ClassicalCode - A::PermGroupRingMatrix + A::GroupAlgebraElemMatrix + GA::GroupAlgebra repr::Function - function LiftedCode(A::Matrix{<: PermGroupRingElem}, repr::Function) - new(A, repr) + function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + all(elem.parent == GA for elem in A) || error("The base ring of all elements in the code must be the same as the group algebra") + new(A, GA, repr) end end -function LiftedCode(A::PermGroupRingMatrix) - !(characteristic(base_ring(A[1,1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra") - LiftedCode(A, permutation_repr) -end +default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) -function LiftedCode(perm_array::Matrix{<:Perm}) - l = length(perm_array[1,1].d) - R = PermutationGroupRing(GF(2), l) - A = Matrix(matrix_space(R, size(perm_array)...)(perm_array)) # convert perms to group ring elements - LiftedCode(A) +""" +The GroupAlgebraElem with `GF(2)` coefficients can be converted to a permutation matrix by `representation_matrix` provided by Hecke. +""" +function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) + !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") + LiftedCode(A; GA=GA, repr=default_repr) end -function LiftedCode(shift_array::Matrix{Int}, l::Int) - perm_array = map(n->cyclic_permutation(n, l), shift_array) - LiftedCode(perm_array) +function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array[1,1])), repr::Union{Function, Nothing}=nothing) + A = zeros(GA, size(group_elem_array)...) + for i in axes(group_elem_array, 1), j in axes(group_elem_array, 2) + A[i, j] = GA[A[i, j]] + end + if repr === nothing + return LiftedCode(A; GA=GA, repr=default_repr) + else + return LiftedCode(A; GA=GA, repr=repr) + end end -""" -Represent a permutation group ring element by mapping permutations to circulant matrices. -""" -function permutation_repr(x::PermGroupRingElem{FqFieldElem}) - mat = zeros(Bool, parent(x).l, parent(x).l) - for k in keys(x.coeffs) - c = Int(lift(ZZ, x.coeffs[k])) # the coefficient of the group element `k`, which is converted to Int type for matrix calculation - mat += c .* Array(matrix_repr(k)) # the coefficient times the matrix representation of the corresponding group element +function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + A = zeros(GA, size(shift_array)...) + for i in 1:size(shift_array, 1) + for j in 1:size(shift_array, 2) + A[i, j] = GA[shift_array[i, j]%l+1] + end end - return mat + return LiftedCode(A; GA=GA, repr=default_repr) end -function lift(repr::Function, mat::PermGroupRingMatrix) +function lift(repr::Function, mat::GroupAlgebraElemMatrix) vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 326757b12..a939686ea 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,5 +1,5 @@ -import Nemo: matrix_space import LinearAlgebra +import Hecke: GroupAlgebra, GroupAlgebraElem, representation_matrix """ Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) @@ -14,41 +14,36 @@ Here, the hypergraph product is taken over a group ring, which serves as the bas After the hypergraph product, the parity-check matrices are lifted by `repr`. The lifting is achieved by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from a group algebra element to a binary matrix. -See also: [`LiftedCode`](@ref), [`PermGroupRing`](@ref). +See also: [`LiftedCode`](@ref). """ - struct LPCode <: AbstractECC - A::PermGroupRingMatrix - B::PermGroupRingMatrix + A::GroupAlgebraElemMatrix + B::GroupAlgebraElemMatrix + GA::GroupAlgebra repr::Function - function LPCode(A::PermGroupRingMatrix, B::PermGroupRingMatrix, repr::Function) - A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") - new(A, B, repr) + function LPCode(A::GroupAlgebraElemMatrix, B::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + all(elem.parent == GA for elem in A) && all(elem.parent == GA for elem in B) || error("The base rings of all elements in both matrices must be the same as the group algebra") + new(A, B, GA, repr) end - function LPCode(c₁::LiftedCode, c₂::LiftedCode, repr::Function) - c₁.A[1, 1].parent == c₂.A[1, 1].parent || error("The base rings of the two codes must be the same") - new(c₁.A, c₂.A, repr) + function LPCode(c₁::LiftedCode, c₂::LiftedCode; GA::GroupAlgebra=c₁.GA, repr::Function=c₁.repr) + # we are using the group algebra and the representation function of the first lifted code + c₁.GA == GA && c₂.GA == GA || error("The base rings of both lifted codes must be the same as the group algebra") + new(c₁.A, c₂.A, GA, repr) end end -function LPCode(A::PermGroupRingMatrix, B::PermGroupRingMatrix) - A[1, 1].parent == B[1, 1].parent || error("The base rings of the two codes must be the same") - LPCode(A, B, permutation_repr) -end - -function LPCode(c₁::LiftedCode, c₂::LiftedCode) - c₁.A[1, 1].parent == c₂.A[1, 1].parent || error("The base rings of the two codes must be the same") - LPCode(c₁, c₂, c₁.repr) # use the same `repr` as the first code +function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) + LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) end -function LPCode(perm_array1::Matrix{<:Perm}, perm_array2::Matrix{<:Perm}) - LPCode(LiftedCode(perm_array1), LiftedCode(perm_array2)) +function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) + LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) end -function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int) - LPCode(LiftedCode(shift_array1, l), LiftedCode(shift_array2, l)) +function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) end iscss(::Type{LPCode}) = true @@ -65,14 +60,14 @@ parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) -code_n(c::LPCode) = size(c.repr(parent(c.A[1, 1])(0)), 2) * (size(c.A, 2) * size(c.B, 2) + size(c.A, 1) * size(c.B, 1)) +code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + size(c.A, 1) * size(c.B, 2)) -code_s(c::LPCode) = size(c.repr(parent(c.A[1, 1])(0)), 1) * (size(c.A, 1) * size(c.B, 2) + size(c.A, 2) * size(c.B, 1)) +code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) """ Two-block group algebra (2GBA) codes. """ -function two_block_group_algebra_codes(a::PermGroupRingElem, b::PermGroupRingElem) +function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) A = reshape([a], (1, 1)) B = reshape([b], (1, 1)) LPCode(A, B) @@ -82,9 +77,9 @@ end Generalized bicycle codes. """ function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) - R = PermutationGroupRing(GF(2), l) - a = sum(R(cyclic_permutation(n, l)) for n in a_shifts) - b = sum(R(cyclic_permutation(n, l)) for n in b_shifts) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n%l+1] for n in a_shifts) + b = sum(GA[n%l+1] for n in b_shifts) two_block_group_algebra_codes(a, b) end @@ -92,7 +87,7 @@ end Bicycle codes. """ function bicycle_codes(a_shifts::Array{Int}, l::Int) - R = PermutationGroupRing(GF(2), l) - a = sum(R(cyclic_permutation(n, l)) for n in a_shifts) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n÷l+1] for n in a_shifts) two_block_group_algebra_codes(a, a') end diff --git a/src/ecc/codes/util.jl b/src/ecc/codes/util.jl index 1063785f9..9949bed9b 100644 --- a/src/ecc/codes/util.jl +++ b/src/ecc/codes/util.jl @@ -6,3 +6,51 @@ function hgp(h₁,h₂) hz = hcat(kron(LinearAlgebra.I(n₁), h₂), kron(h₁', LinearAlgebra.I(r₂))) hx, hz end + +import AbstractAlgebra: Group, GroupElem, AdditiveGroup, AdditiveGroupElem +import Hecke: GroupAlgebra, GroupAlgebraElem, dim, base_ring, multiplication_table, coefficients +import Base: adjoint +import LinearAlgebra + +""" +Compute the adjoint of a group algebra element. +The adjoint is defined as the conjugate of the element in the group algebra, +i.e. the inverse of the element in the associated group. +""" +function adjoint(a::GroupAlgebraElem{T}) where T + A = parent(a) + d = dim(A) + v = Vector{T}(undef, d) + for i in 1:d + v[i] = zero(base_ring(A)) + end + id_index = findfirst(x -> x == 1, one(A).coeffs) + # t = zero(base_ring(A)) + mt = multiplication_table(A, copy = false) + acoeff = coefficients(a, copy = false) + for i in 1:d + if acoeff[i] != 0 + k = findfirst(x -> x==id_index, mt[i, :]) # find the inverse of i-th element in the group + v[k] += acoeff[i] + end + end + return A(v) +end + +""" +The difference between Group and AdditiveGroup is that the former one uses * operations while the latter uses + operations; +they are all supported by Hecke.GroupAlgebraElem +""" +# const GroupOrAdditiveGroup = Union{Group,AdditiveGroup} + +const GroupOrAdditiveGroupElem = Union{GroupElem,AdditiveGroupElem} + +const GroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem,<:Matrix{<:GroupAlgebraElem}} +} + +const FqFieldGroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra},<:Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}} +} diff --git a/src/ecc/group_ring.jl b/src/ecc/group_ring.jl deleted file mode 100644 index 1e23302fc..000000000 --- a/src/ecc/group_ring.jl +++ /dev/null @@ -1,309 +0,0 @@ -import Base: *, +, -, ^, ==, deepcopy_internal, show, isone, iszero, one, zero, adjoint - -import Nemo: Ring, RingElem, RingElement, NCRing, NCRingElem, NCRingElement, Perm, CacheDictType, - @attributes, base_ring, base_ring_type, elem_type, get_cached!, - is_domain_type, is_exact_type, parent, parent_type - - -""" -Permutation [group ring](https://en.wikipedia.org/wiki/Group_ring) over a base ring, also known as group algebra. - -- `base_ring`: The base ring, whose elements are used as coefficients of permutations. -- `l`: The length of permutations. - -Basic usage: - -- To construct a permutation group ring, use [`PermutationGroupRing`](@ref). -- To construct an element of a permutation group ring, use the call syntax of the parent object. -- To perform arithmetic operations, use the standard arithmetic operators. - -```jldoctest -julia> ENV["NEMO_PRINT_BANNER"] = "false"; using Nemo: GF, Perm - -julia> R = PermutationGroupRing(GF(2), 3) -Permutation group ring over Prime field of characteristic 2 - -julia> f0 = R(0) -Dict{Perm, Nemo.FqFieldElem}() - -julia> f1 = R(1) -Dict{Perm{Int64}, Nemo.FqFieldElem}(() => 1) - -julia> f2 = R(Perm([1,3,2])) -Dict{Perm{Int64}, Nemo.FqFieldElem}((2,3) => 1) - -julia> f3 = R(Dict(Perm(3) => 1, Perm([3,1,2]) => 1)) -Dict{Perm{Int64}, Nemo.FqFieldElem}(() => 1, (1,3,2) => 1) - -julia> f1 + f2 -Dict{Perm, Nemo.FqFieldElem}(() => 1, (2,3) => 1) - -julia> f2 * f3 -Dict{Perm, Nemo.FqFieldElem}((1,3) => 1, (2,3) => 1) - -julia> f3 * 1 + 1 -Dict{Perm, Nemo.FqFieldElem}((1,3,2) => 1) - -julia> f3' -Dict{Perm, Nemo.FqFieldElem}(() => 1, (1,2,3) => 1) -``` - -See also: [`PermGroupRingElem`](@ref). -""" -@attributes mutable struct PermGroupRing{T<:RingElement} <: NCRing # TODO if this is only used for ECC, maybe we should consider fixing the base ring to be Z2 - base_ring::Ring - l::Int - - function PermGroupRing{T}(R::Ring, l::Int, cached::Bool) where {T<:RingElement} - return get_cached!(PermGroupRingElemID, (R, l), cached) do - new{T}(R, l) - end::PermGroupRing{T} - end -end - -const PermGroupRingElemID = CacheDictType{Tuple{Ring, Int}, NCRing}() - -""" -Element of a [`PermGroupRing`](@ref). - -- `coeffs`: A dictionary of permutations and their coefficients. Empty dictionary represents zero. -- `parent`: The parent group ring in type `PermGroupRing`. - -See also: [`PermGroupRing`](@ref). -""" -mutable struct PermGroupRingElem{T<:RingElement} <: NCRingElem - coeffs::Dict{<:Perm,T} - parent::PermGroupRing{T} - - function PermGroupRingElem{T}(coeffs::Dict{<:Perm,T}) where {T<:RingElement} - filter!(x -> !iszero(x[2]) , coeffs) # remove zeros - return new{T}(coeffs) - end - - function PermGroupRingElem{T}() where {T<:RingElement} - return new{T}(Dict{Perm,T}()) - end -end - -# Data type and parent object methods - -parent_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = PermGroupRing{T} - -elem_type(::Type{PermGroupRing{T}}) where {T<:RingElement} = PermGroupRingElem{T} - -base_ring_type(::Type{PermGroupRing{T}}) where {T<:RingElement} = parent_type(T) - -base_ring(R::PermGroupRing) = R.base_ring::base_ring_type(R) - -parent(f::PermGroupRingElem) = f.parent - -is_domain_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_domain_type(T) - -is_exact_type(::Type{PermGroupRingElem{T}}) where {T<:RingElement} = is_exact_type(T) - -function deepcopy_internal(f::PermGroupRingElem{T}, dict::IdDict) where {T<:RingElement} - r = PermGroupRingElem{T}(deepcopy_internal(f.coeffs, dict)) - r.parent = f.parent # parent should not be deepcopied - return r -end - -# Basic manipulation - -zero(R::PermGroupRing) = R() - -one(R::PermGroupRing) = R(1) - -iszero(f::PermGroupRingElem) = f == 0 - -isone(f::PermGroupRingElem) = f == 1 - -# String I/O - -function show(io::IO, R::PermGroupRing) - print(io, "Permutation group ring over ") - show(io, base_ring(R)) - end - - function show(io::IO, f::PermGroupRingElem) - print(io, f.coeffs) - end - -# Arithmetic functions - -function -(a::PermGroupRingElem{T}) where {T<:RingElement} - r = parent(a)() - for (k, v) in a.coeffs - r.coeffs[k] = -v - end - return r -end - -function +(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} - r = parent(a)() - for (k, v) in a.coeffs - r.coeffs[k] = v - end - for (k, v) in b.coeffs - if haskey(r.coeffs, k) - r.coeffs[k] += v - if r.coeffs[k] == 0 - delete!(r.coeffs, k) - end - else - r.coeffs[k] = v - end - end - return r -end - --(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} = a + (-b) - -function *(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} - r = parent(a)() - for (k1, v1) in a.coeffs - for (k2, v2) in b.coeffs - k = k1 * k2 - if haskey(r.coeffs, k) - r.coeffs[k] += v1 * v2 - else - r.coeffs[k] = v1 * v2 - end - end - end - filter!(x -> x[2] != 0, r.coeffs) - return r -end - -function ^(a::PermGroupRingElem{T}, n::Int) where {T<:RingElement} - if n == 0 - return one(parent(a)) - elseif n == 1 - return a - elseif n < 0 - DomainError(n) - end - r = *(repeat([a], n)...) - return r -end - -# Ad hoc arithmetic functions - -*(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = a * parent(a)(n) - -*(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a * n - -+(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = a + parent(a)(n) - -+(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a + n - -*(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = a * parent(a)(p) - -*(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = parent(a)(p) * a - -+(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = a + parent(a)(p) - -+(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a + p - -# Comparison - -function ==(a::PermGroupRingElem{T}, b::PermGroupRingElem{T}) where {T<:RingElement} - if length(a.coeffs) != length(b.coeffs) - return false - end - for (k, v) in a.coeffs - if !haskey(b.coeffs, k) || b.coeffs[k] != v - return false - end - end - return true -end - -# Ad hoc comparison - -==(a::PermGroupRingElem{T}, n::T) where {T<:RingElement} = length(a.coeffs) == 1 && a.coeffs[Perm(parent(a).l)] == n - -==(n::T, a::PermGroupRingElem{T}) where {T<:RingElement} = a == n - -==(a::PermGroupRingElem{T}, p::Perm) where {T<:RingElement} = length(a.coeffs) == 1 && a.coeffs[p] == base_ring(parent(a))(1) - -==(p::Perm, a::PermGroupRingElem{T}) where {T<:RingElement} = a == p - -# TODO Some functionality are expected by ring interfaces but not necessary for ECC construction, -# including hash, exact division, random generation, promotion rules - -# Constructors by overloading the call syntax for parent objects - -function (R::PermGroupRing{T})() where {T<:RingElement} - r = PermGroupRingElem{T}() - r.parent = R - return r -end - -function (R::PermGroupRing{T})(coeffs::Dict{<:Perm,<:Union{T,Integer,Rational,AbstractFloat}}) where {T<:RingElement} - if valtype(coeffs) == T - for (k,v) in coeffs - length(k.d) == R.l || error("Invalid permutation length") - parent(v) == R || error("Unable to coerce a group ring element") - end - else - coeffs = Dict(k => base_ring(R)(v) for (k, v) in coeffs) - end - r = PermGroupRingElem{T}(coeffs) - r.parent = R - return r -end - -function (R::PermGroupRing{T})(n::Union{Integer,Rational,AbstractFloat}) where {T<:RingElement} - coeffs = iszero(n) ? Dict{Perm,T}() : Dict(Perm(R.l) => base_ring(R)(n)) - r = PermGroupRingElem{T}(coeffs) - r.parent = R - return r -end - -function (R::PermGroupRing{T})(n::T) where {T<:RingElement} - base_ring(R) == parent(n) || error("Unable to coerce a group ring element") - r = PermGroupRingElem{T}(Dict(Perm(R.l) => n)) - r.parent = R - return r -end - -function (R::PermGroupRing{T})(p::Perm) where {T<:RingElement} - length(p.d) == R.l || error("Invalid permutation length") - r = PermGroupRingElem{T}(Dict(p => one(base_ring(R)))) - r.parent = R - return r -end - -function (R::PermGroupRing{T})(f::PermGroupRingElem{T}) where {T<:RingElement} - parent(f) == R || error("Unable to coerce a group ring element") - return f -end - -""" -Permutation group ring constructor. - -See also: [`PermutationGroupRing`](@ref). -""" -function PermutationGroupRing(R::Ring, l::Int, cached::Bool=true) - T = elem_type(R) - return PermGroupRing{T}(R, l, cached) -end - -# adjoint - -function adjoint(a::PermGroupRingElem{T}) where {T<:FqFieldElem} - r = parent(a)() - for (k, v) in a.coeffs - r.coeffs[inv(k)] = v - end - return r -end - -""" -Construct a cyclic permutation of length `l` with a shift `n`. -""" -cyclic_permutation(n::Int, l::Int) = Perm(vcat(n+1:l, 1:n)) - -PermGroupRingMatrix = Union{ - Matrix{<:PermGroupRingElem}, - LinearAlgebra.Adjoint{<:PermGroupRingElem,<:Matrix{<:PermGroupRingElem}}} diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index e2595c0be..6462db355 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -17,6 +17,7 @@ random_circuit_code_args = vcat( [map(f -> getfield(random_all_to_all_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_all_to_all_circuit_args] ) +# TODO benchmarking them in wiki # test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 B04 = Dict( 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], @@ -33,7 +34,6 @@ B118 = Dict( LP04 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B04] LP118 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B118] -# benchmarking wiki # generalized bicyle codes @@ -46,20 +46,17 @@ other_lifted_product_codes = [] # from https://arxiv.org/abs/2202.01702v3 l = 63 -R = PermutationGroupRing(GF(2), l) -A = zeros(R, 7, 7) -x = R(cyclic_permutation(1, l)) +GA = group_algebra(GF(2), abelian_group(l)) +A = zeros(GA, 7, 7) +x = gens(GA)[] A[LinearAlgebra.diagind(A)] .= x^27 A[LinearAlgebra.diagind(A, -1)] .= x^54 A[LinearAlgebra.diagind(A, 6)] .= x^54 -A[LinearAlgebra.diagind(A, -2)] .= R(1) -A[LinearAlgebra.diagind(A, 5)] .= R(1) +A[LinearAlgebra.diagind(A, -2)] .= GA(1) +A[LinearAlgebra.diagind(A, 5)] .= GA(1) B = reshape([1 + x + x^6], (1, 1)) -# x^63 == 1 -# how is this not polynomial... - push!(other_lifted_product_codes, LPCode(A, B)) const code_instance_args = Dict( diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index f87747096..e916c0447 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -31,7 +31,7 @@ @test nqubits(code) == size(H, 2) == code_n(code) @test size(H, 1) == code_s(code) @test code_s(code) + code_k(code) >= code_n(code) # possbly exist redundant checks - @test size(H, 1) < size(H, 2) + @test size(H, 1) <= size(H, 2) # equal in some cases with redundancy @test QuantumClifford.stab_looks_good(copy(H)) end end From 7be3bfdce7de775e7a068c82030636d27392904b Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Mon, 9 Sep 2024 19:59:41 +0900 Subject: [PATCH 40/67] import Hecke functions in tests --- test/test_ecc_base.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 6462db355..673abae7a 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -5,6 +5,7 @@ using InteractiveUtils import Nemo: GF import LinearAlgebra +import Hecke: group_algebra, abelian_group # generate instances of all implemented codes to make sure nothing skips being checked From fe3f78a50f418524f2419128fafbd2ffc5d40015 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Mon, 9 Sep 2024 21:10:05 +0900 Subject: [PATCH 41/67] add gens imports --- test/test_ecc_base.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 673abae7a..43aeda1f0 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -5,7 +5,7 @@ using InteractiveUtils import Nemo: GF import LinearAlgebra -import Hecke: group_algebra, abelian_group +import Hecke: group_algebra, abelian_group, gens # generate instances of all implemented codes to make sure nothing skips being checked From a854e6d24aaf50459f3fb10f167b84e9e9561a0e Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Mon, 9 Sep 2024 22:40:42 +0900 Subject: [PATCH 42/67] Move Hecke-related things to ext --- Project.toml | 7 +- .../QuantumCliffordHeckeExt.jl | 17 +++ ext/QuantumCliffordHeckeExt/lifted.jl | 78 +++++++++++++ ext/QuantumCliffordHeckeExt/lifted_product.jl | 90 +++++++++++++++ ext/QuantumCliffordHeckeExt/types.jl | 36 ++++++ src/ecc/codes/classical/lifted.jl | 85 +------------- src/ecc/codes/lifted_product.jl | 108 ++++-------------- src/ecc/codes/util.jl | 48 -------- 8 files changed, 254 insertions(+), 215 deletions(-) create mode 100644 ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl create mode 100644 ext/QuantumCliffordHeckeExt/lifted.jl create mode 100644 ext/QuantumCliffordHeckeExt/lifted_product.jl create mode 100644 ext/QuantumCliffordHeckeExt/types.jl diff --git a/Project.toml b/Project.toml index 992c1bea9..3db9c6d23 100644 --- a/Project.toml +++ b/Project.toml @@ -4,12 +4,10 @@ authors = ["Stefan Krastanov and QuantumSavory community version = "0.9.8" [deps] -AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" HostCPUFeatures = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" ILog2 = "2cd5bd5f-40a1-5050-9e10-fc8cdb6109f5" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" @@ -25,7 +23,9 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" [weakdeps] +AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" LDPCDecoders = "3c486d74-64b9-4c60-8b1a-13a564e77efb" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" @@ -35,6 +35,7 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" [extensions] QuantumCliffordGPUExt = "CUDA" +QuantumCliffordHeckeExt = ["AbstractAlgebra", "Hecke"] QuantumCliffordLDPCDecodersExt = "LDPCDecoders" QuantumCliffordMakieExt = "Makie" QuantumCliffordPlotsExt = "Plots" @@ -43,11 +44,13 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase" QuantumCliffordQuantikzExt = "Quantikz" [compat] +AbstractAlgebra = "0.42" CUDA = "4.4.0, 5" Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" +Hecke = "0.33" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl new file mode 100644 index 000000000..6d5c46771 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -0,0 +1,17 @@ +module QuantumCliffordHeckeExt + +import QuantumClifford, LinearAlgebra +import AbstractAlgebra: Group, GroupElem, AdditiveGroup, AdditiveGroupElem +import Hecke: GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, + multiplication_table, coefficients, abelian_group, group_algebra +import Base: adjoint +import Nemo: characteristic, lift, matrix_repr, GF, ZZ + +import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, + hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz + +include("types.jl") +include("lifted.jl") +include("lifted_product.jl") + +end # module \ No newline at end of file diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl new file mode 100644 index 000000000..2fa347958 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -0,0 +1,78 @@ +""" +Classical codes lifted over a group algebra [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). + +- `A::Matrix`: the base matrix of the code, whose elements are in a group algebra. +- `repr::Function`: a function that converts a group algebra element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. + +TODO why we need such a freedom of representation? + +The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. +This will enlarge the parity check matrix from `A` with each element being inflated into a matrix. The procedure is called a lift [panteleev2022asymptotically](@cite). + +See also: [`LPCode`](@ref). +""" +struct LiftedCode <: ClassicalCode + A::GroupAlgebraElemMatrix + GA::GroupAlgebra + repr::Function + + function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + all(elem.parent == GA for elem in A) || error("The base ring of all elements in the code must be the same as the group algebra") + new(A, GA, repr) + end +end + +default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) + +""" +The GroupAlgebraElem with `GF(2)` coefficients can be converted to a permutation matrix by `representation_matrix` provided by Hecke. +""" +function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) + !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") + LiftedCode(A; GA=GA, repr=default_repr) +end + +function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array[1,1])), repr::Union{Function, Nothing}=nothing) + A = zeros(GA, size(group_elem_array)...) + for i in axes(group_elem_array, 1), j in axes(group_elem_array, 2) + A[i, j] = GA[A[i, j]] + end + if repr === nothing + return LiftedCode(A; GA=GA, repr=default_repr) + else + return LiftedCode(A; GA=GA, repr=repr) + end +end + +function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + A = zeros(GA, size(shift_array)...) + for i in 1:size(shift_array, 1) + for j in 1:size(shift_array, 2) + A[i, j] = GA[shift_array[i, j]%l+1] + end + end + return LiftedCode(A; GA=GA, repr=default_repr) +end + +function lift(repr::Function, mat::GroupAlgebraElemMatrix) + vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) +end + +function parity_checks(c::LiftedCode) + h = lift(c.repr, c.A) + rk = mod2rank(h) # TODO mod2rank and canonicalize, which is more efficient? + rk < size(h, 1) && @warn "The lifted code has redundant rows" + return h +end + +code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) + +function mod2rank(h::Matrix{<:Integer}) # TODO move this mod2rank to a common place + Z2, _ = residue_ring(ZZ, 2) + S = matrix_space(Z2, size(h)...) + rank(S(h)) +end + +code_s(c::LiftedCode) = mod2rank(parity_checks(c)) + +code_k(c::LiftedCode) = code_n(c) - code_s(c) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl new file mode 100644 index 000000000..08d931a08 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -0,0 +1,90 @@ +""" +Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) + +- `A::Matrix{PermGroupRingElem}`: the first base matrix for constructing the lifted product code, whose elements are in a permutation group ring; +- `B::Matrix{PermGroupRingElem}`: the second base matrix for constructing the lifted product code, whose elements are in the same permutation group ring as `A`; +- `repr::Function`: a function that converts the permutation group ring element to a matrix; + default to be [`permutation_repr`](@ref) for GF(2)-algebra. + +A lifted product code is constructed by hypergraph product of the two lifted codes `c₁` and `c₂`. +Here, the hypergraph product is taken over a group ring, which serves as the base ring for both lifted codes. +After the hypergraph product, the parity-check matrices are lifted by `repr`. +The lifting is achieved by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from a group algebra element to a binary matrix. + +See also: [`LiftedCode`](@ref). +""" +struct LPCode <: AbstractECC + A::GroupAlgebraElemMatrix + B::GroupAlgebraElemMatrix + GA::GroupAlgebra + repr::Function + + function LPCode(A::GroupAlgebraElemMatrix, B::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + all(elem.parent == GA for elem in A) && all(elem.parent == GA for elem in B) || error("The base rings of all elements in both matrices must be the same as the group algebra") + new(A, B, GA, repr) + end + + function LPCode(c₁::LiftedCode, c₂::LiftedCode; GA::GroupAlgebra=c₁.GA, repr::Function=c₁.repr) + # we are using the group algebra and the representation function of the first lifted code + c₁.GA == GA && c₂.GA == GA || error("The base rings of both lifted codes must be the same as the group algebra") + new(c₁.A, c₂.A, GA, repr) + end +end + +function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) + LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) +end + +function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) + LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) +end + +function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) +end + +iscss(::Type{LPCode}) = true + +function parity_checks_xz(c::LPCode) + hx, hz = hgp(c.A, c.B') + hx, hz = lift(c.repr, hx), lift(c.repr, hz) + return hx, hz +end + +parity_checks_x(c::LPCode) = parity_checks_xz(c)[1] + +parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] + +parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) + +code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + size(c.A, 1) * size(c.B, 2)) + +code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) + +""" +Two-block group algebra (2GBA) codes. +""" +function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) + A = reshape([a], (1, 1)) + B = reshape([b], (1, 1)) + LPCode(A, B) +end + +""" +Generalized bicycle codes. +""" +function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n%l+1] for n in a_shifts) + b = sum(GA[n%l+1] for n in b_shifts) + two_block_group_algebra_codes(a, b) +end + +""" +Bicycle codes. +""" +function bicycle_codes(a_shifts::Array{Int}, l::Int) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n÷l+1] for n in a_shifts) + two_block_group_algebra_codes(a, a') +end diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl new file mode 100644 index 000000000..667a89b68 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/types.jl @@ -0,0 +1,36 @@ +const GroupOrAdditiveGroupElem = Union{GroupElem,AdditiveGroupElem} + +const GroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem,<:Matrix{<:GroupAlgebraElem}} +} + +const FqFieldGroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra},<:Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}} +} + +""" +Compute the adjoint of a group algebra element. +The adjoint is defined as the conjugate of the element in the group algebra, +i.e. the inverse of the element in the associated group. +""" +function adjoint(a::GroupAlgebraElem{T}) where T + A = parent(a) + d = dim(A) + v = Vector{T}(undef, d) + for i in 1:d + v[i] = zero(base_ring(A)) + end + id_index = findfirst(x -> x == 1, one(A).coeffs) + # t = zero(base_ring(A)) + mt = multiplication_table(A, copy = false) + acoeff = coefficients(a, copy = false) + for i in 1:d + if acoeff[i] != 0 + k = findfirst(x -> x==id_index, mt[i, :]) # find the inverse of i-th element in the group + v[k] += acoeff[i] + end + end + return A(v) +end diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 0c2e357f3..d7a9b1d40 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,82 +1,7 @@ -import Nemo: characteristic, lift, matrix_repr -import Hecke: representation_matrix, GroupAlgebra, GroupAlgebraElem, GroupElem, abelian_group, group_algebra - - -""" -Classical codes lifted over a group algebra [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). - -- `A::Matrix`: the base matrix of the code, whose elements are in a group algebra. -- `repr::Function`: a function that converts a group algebra element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. - -TODO why we need such a freedom of representation? - -The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. -This will enlarge the parity check matrix from `A` with each element being inflated into a matrix. The procedure is called a lift [panteleev2022asymptotically](@cite). - -See also: [`LPCode`](@ref). -""" -struct LiftedCode <: ClassicalCode - A::GroupAlgebraElemMatrix - GA::GroupAlgebra - repr::Function - - function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) - all(elem.parent == GA for elem in A) || error("The base ring of all elements in the code must be the same as the group algebra") - new(A, GA, repr) +function LiftedCode(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `LiftedCode` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `LiftedCode` will be available.") end + return ext.LiftedCode(args...; kwargs...) end - -default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) - -""" -The GroupAlgebraElem with `GF(2)` coefficients can be converted to a permutation matrix by `representation_matrix` provided by Hecke. -""" -function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) - !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") - LiftedCode(A; GA=GA, repr=default_repr) -end - -function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array[1,1])), repr::Union{Function, Nothing}=nothing) - A = zeros(GA, size(group_elem_array)...) - for i in axes(group_elem_array, 1), j in axes(group_elem_array, 2) - A[i, j] = GA[A[i, j]] - end - if repr === nothing - return LiftedCode(A; GA=GA, repr=default_repr) - else - return LiftedCode(A; GA=GA, repr=repr) - end -end - -function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) - A = zeros(GA, size(shift_array)...) - for i in 1:size(shift_array, 1) - for j in 1:size(shift_array, 2) - A[i, j] = GA[shift_array[i, j]%l+1] - end - end - return LiftedCode(A; GA=GA, repr=default_repr) -end - -function lift(repr::Function, mat::GroupAlgebraElemMatrix) - vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) -end - -function parity_checks(c::LiftedCode) - h = lift(c.repr, c.A) - rk = mod2rank(h) # TODO mod2rank and canonicalize, which is more efficient? - rk < size(h, 1) && @warn "The lifted code has redundant rows" - return h -end - -code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) - -function mod2rank(h::Matrix{<:Integer}) # TODO move this mod2rank to a common place - Z2, _ = residue_ring(ZZ, 2) - S = matrix_space(Z2, size(h)...) - rank(S(h)) -end - -code_s(c::LiftedCode) = mod2rank(parity_checks(c)) - -code_k(c::LiftedCode) = code_n(c) - code_s(c) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index a939686ea..c695f9d67 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,93 +1,31 @@ -import LinearAlgebra -import Hecke: GroupAlgebra, GroupAlgebraElem, representation_matrix - -""" -Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) - -- `A::Matrix{PermGroupRingElem}`: the first base matrix for constructing the lifted product code, whose elements are in a permutation group ring; -- `B::Matrix{PermGroupRingElem}`: the second base matrix for constructing the lifted product code, whose elements are in the same permutation group ring as `A`; -- `repr::Function`: a function that converts the permutation group ring element to a matrix; - default to be [`permutation_repr`](@ref) for GF(2)-algebra. - -A lifted product code is constructed by hypergraph product of the two lifted codes `c₁` and `c₂`. -Here, the hypergraph product is taken over a group ring, which serves as the base ring for both lifted codes. -After the hypergraph product, the parity-check matrices are lifted by `repr`. -The lifting is achieved by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from a group algebra element to a binary matrix. - -See also: [`LiftedCode`](@ref). -""" -struct LPCode <: AbstractECC - A::GroupAlgebraElemMatrix - B::GroupAlgebraElemMatrix - GA::GroupAlgebra - repr::Function - - function LPCode(A::GroupAlgebraElemMatrix, B::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) - all(elem.parent == GA for elem in A) && all(elem.parent == GA for elem in B) || error("The base rings of all elements in both matrices must be the same as the group algebra") - new(A, B, GA, repr) - end - - function LPCode(c₁::LiftedCode, c₂::LiftedCode; GA::GroupAlgebra=c₁.GA, repr::Function=c₁.repr) - # we are using the group algebra and the representation function of the first lifted code - c₁.GA == GA && c₂.GA == GA || error("The base rings of both lifted codes must be the same as the group algebra") - new(c₁.A, c₂.A, GA, repr) +function LPCode(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `LPCode` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `LPCode` will be available.") end + return ext.LPCode(args...; kwargs...) end -function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) - LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) -end - -function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) - LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) -end - -function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) - LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) -end - -iscss(::Type{LPCode}) = true - -function parity_checks_xz(c::LPCode) - hx, hz = hgp(c.A, c.B') - hx, hz = lift(c.repr, hx), lift(c.repr, hz) - return hx, hz -end - -parity_checks_x(c::LPCode) = parity_checks_xz(c)[1] - -parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] - -parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) - -code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + size(c.A, 1) * size(c.B, 2)) - -code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) - -""" -Two-block group algebra (2GBA) codes. -""" -function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) - A = reshape([a], (1, 1)) - B = reshape([b], (1, 1)) - LPCode(A, B) +function two_block_group_algebra_codes(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `two_block_group_algebra_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `two_block_group_algebra_codes` will be available.") + end + return ext.two_block_group_algebra_codes(args...; kwargs...) end -""" -Generalized bicycle codes. -""" -function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) - GA = group_algebra(GF(2), abelian_group(l)) - a = sum(GA[n%l+1] for n in a_shifts) - b = sum(GA[n%l+1] for n in b_shifts) - two_block_group_algebra_codes(a, b) +function generalized_bicycle_codes(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `generalized_bicycle_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `generalized_bicycle_codes` will be available.") + end + return ext.generalized_bicycle_codes(args...; kwargs...) end -""" -Bicycle codes. -""" -function bicycle_codes(a_shifts::Array{Int}, l::Int) - GA = group_algebra(GF(2), abelian_group(l)) - a = sum(GA[n÷l+1] for n in a_shifts) - two_block_group_algebra_codes(a, a') +function bicycle_codes(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `bicycle_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `bicycle_codes` will be available.") + end + return ext.bicycle_codes(args...; kwargs...) end diff --git a/src/ecc/codes/util.jl b/src/ecc/codes/util.jl index 9949bed9b..1063785f9 100644 --- a/src/ecc/codes/util.jl +++ b/src/ecc/codes/util.jl @@ -6,51 +6,3 @@ function hgp(h₁,h₂) hz = hcat(kron(LinearAlgebra.I(n₁), h₂), kron(h₁', LinearAlgebra.I(r₂))) hx, hz end - -import AbstractAlgebra: Group, GroupElem, AdditiveGroup, AdditiveGroupElem -import Hecke: GroupAlgebra, GroupAlgebraElem, dim, base_ring, multiplication_table, coefficients -import Base: adjoint -import LinearAlgebra - -""" -Compute the adjoint of a group algebra element. -The adjoint is defined as the conjugate of the element in the group algebra, -i.e. the inverse of the element in the associated group. -""" -function adjoint(a::GroupAlgebraElem{T}) where T - A = parent(a) - d = dim(A) - v = Vector{T}(undef, d) - for i in 1:d - v[i] = zero(base_ring(A)) - end - id_index = findfirst(x -> x == 1, one(A).coeffs) - # t = zero(base_ring(A)) - mt = multiplication_table(A, copy = false) - acoeff = coefficients(a, copy = false) - for i in 1:d - if acoeff[i] != 0 - k = findfirst(x -> x==id_index, mt[i, :]) # find the inverse of i-th element in the group - v[k] += acoeff[i] - end - end - return A(v) -end - -""" -The difference between Group and AdditiveGroup is that the former one uses * operations while the latter uses + operations; -they are all supported by Hecke.GroupAlgebraElem -""" -# const GroupOrAdditiveGroup = Union{Group,AdditiveGroup} - -const GroupOrAdditiveGroupElem = Union{GroupElem,AdditiveGroupElem} - -const GroupAlgebraElemMatrix = Union{ - Matrix{<:GroupAlgebraElem}, - LinearAlgebra.Adjoint{<:GroupAlgebraElem,<:Matrix{<:GroupAlgebraElem}} -} - -const FqFieldGroupAlgebraElemMatrix = Union{ - Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}, - LinearAlgebra.Adjoint{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra},<:Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}} -} From 16727f0522891c5febfc6dd17c85b974f79fdf1c Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Mon, 9 Sep 2024 23:11:22 +0900 Subject: [PATCH 43/67] fix code parameters for lifted codes --- ext/QuantumCliffordHeckeExt/lifted.jl | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 2fa347958..5cfc4bf43 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -59,20 +59,9 @@ function lift(repr::Function, mat::GroupAlgebraElemMatrix) end function parity_checks(c::LiftedCode) - h = lift(c.repr, c.A) - rk = mod2rank(h) # TODO mod2rank and canonicalize, which is more efficient? - rk < size(h, 1) && @warn "The lifted code has redundant rows" - return h + return lift(c.repr, c.A) end -code_n(c::LiftedCode) = size(c.A, 2) * size(c.repr(parent(c.A[1,1])(0)), 2) +code_n(c::LiftedCode) = size(c.A, 2) * size(zero(c.GA), 2) -function mod2rank(h::Matrix{<:Integer}) # TODO move this mod2rank to a common place - Z2, _ = residue_ring(ZZ, 2) - S = matrix_space(Z2, size(h)...) - rank(S(h)) -end - -code_s(c::LiftedCode) = mod2rank(parity_checks(c)) - -code_k(c::LiftedCode) = code_n(c) - code_s(c) +code_s(c::LiftedCode) = size(c.A, 1) * size(zero(c.GA), 1) From a7d0d8f43b75d9038c5d9deb893db37c45244216 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 00:04:05 +0900 Subject: [PATCH 44/67] enrich docstrings --- ext/QuantumCliffordHeckeExt/lifted.jl | 42 ++++++++++++-- ext/QuantumCliffordHeckeExt/lifted_product.jl | 57 +++++++++++++++---- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 5cfc4bf43..7c9c96c02 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -1,22 +1,52 @@ """ +$TYPEDEF + Classical codes lifted over a group algebra [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). -- `A::Matrix`: the base matrix of the code, whose elements are in a group algebra. -- `repr::Function`: a function that converts a group algebra element to a matrix; default to be [`permutation_repr`](@ref) for GF(2)-algebra. +The parity-check matrix is constructed by applying `repr` to each element of `A`, +which is mathematically a linear map from a group algebra element to a binary matrix. +The size of the parity check matrix will enlarged with each element of `A` being inflated into a matrix. +The procedure is called a lift [panteleev2022asymptotically](@cite). + +## Constructors + +A lifted code can be constructed via the following approaches: + +1. A matrix of group algebra elements. + +2. A matrix of group elements, where a group element will be considered as a group algebra element by assigning a unit coefficient. -TODO why we need such a freedom of representation? +3. A matrix of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. -The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. -This will enlarge the parity check matrix from `A` with each element being inflated into a matrix. The procedure is called a lift [panteleev2022asymptotically](@cite). +The default `GA` is the group algebra of `A[1, 1]`, the default representation is the permutation representation. + +## The representation function + +In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +The default representation, provided by `Hecke`, is the permutation representation. + +We also accept a custom representation function. +Such a customization would be useful to reduce the number of bits required by the code construction. + +For example, if we use a D4 group for lifting. In our default way, the representation will be `8×8` matrices, +where 8 is the group's order. We can find a `4×4` matrix representation for the group, +with details in [this discussion](https://github.com/QuantumSavory/QuantumClifford.jl/pull/312#discussion_r1682721136). See also: [`LPCode`](@ref). + +$TYPEDFIELDS """ struct LiftedCode <: ClassicalCode + """the base matrix of the code, whose elements are in a group algebra.""" A::GroupAlgebraElemMatrix + """the group algebra for which elements in `A` are from.""" GA::GroupAlgebra + """ + a function that converts a group algebra element to a binary matrix; + default to be the permutation representation for GF(2)-algebra.""" repr::Function - function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1, 1]), repr::Function) all(elem.parent == GA for elem in A) || error("The base ring of all elements in the code must be the same as the group algebra") new(A, GA, repr) end diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 08d931a08..50dd35a51 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -1,22 +1,44 @@ """ +$TYPEDEF + Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) -- `A::Matrix{PermGroupRingElem}`: the first base matrix for constructing the lifted product code, whose elements are in a permutation group ring; -- `B::Matrix{PermGroupRingElem}`: the second base matrix for constructing the lifted product code, whose elements are in the same permutation group ring as `A`; -- `repr::Function`: a function that converts the permutation group ring element to a matrix; - default to be [`permutation_repr`](@ref) for GF(2)-algebra. +A lifted product code is defined by the hypergraph product of a base matrices `A` and the conjugate of another base matrix `B'`. +Here, the hypergraph product is taken over a group algebra, of which the base matrices are consisting. + +The parity check matrix is obtained by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from each group algebra element to a binary matrix. + +## Constructors + +1. Two base matrices of group algebra elements. + +2. Two lifted codes, whose base matrices are for quantum code construction. + +3. Two base matrices of group elements, where each group element will be considered as a group algebra element by assigning a unit coefficient. + +4. Two base matrices of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. -A lifted product code is constructed by hypergraph product of the two lifted codes `c₁` and `c₂`. -Here, the hypergraph product is taken over a group ring, which serves as the base ring for both lifted codes. -After the hypergraph product, the parity-check matrices are lifted by `repr`. -The lifting is achieved by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from a group algebra element to a binary matrix. +## The representation function -See also: [`LiftedCode`](@ref). +In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +The default representation, provided by `Hecke`, is the permutation representation. + +We also accept a custom representation function. The reasons are detailed in [`LiftedCode`](@ref). + +See also: [`LiftedCode`](@ref), [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). + +$TYPEDFIELDS """ struct LPCode <: AbstractECC + """the first base matrix of the code, whose elements are in a group algebra.""" A::GroupAlgebraElemMatrix + """the second base matrix of the code, whose elements are in the same group algebra as `A`.""" B::GroupAlgebraElemMatrix + """the group algebra for which elements in `A` and `B` are from.""" GA::GroupAlgebra + """ + a function that converts a group algebra element to a binary matrix; + default to be the permutation representation for GF(2)-algebra.""" repr::Function function LPCode(A::GroupAlgebraElemMatrix, B::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) @@ -62,7 +84,10 @@ code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) """ -Two-block group algebra (2GBA) codes. +Two-block group algebra (2GBA) codes, which are a special case of lifted product codes +from two group algebra elements `a` and `b`, used as `1x1` base matrices. + +See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref) """ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) A = reshape([a], (1, 1)) @@ -71,7 +96,11 @@ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) end """ -Generalized bicycle codes. +Generalized bicycle codes, which are a special case of 2GBA codes (and therefore of lifted product codes). +Here the group is choosen as the cyclic group of order `l`, +and the base matrices `a` and `b` are the sum of the group algebra elements corresponding to the shifts `a_shifts` and `b_shifts`. + +See also: [`two_block_group_algebra_codes`](@ref), [`bicycle_codes`](@ref). """ function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) @@ -81,7 +110,11 @@ function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l end """ -Bicycle codes. +Bicycle codes are a special case of generalized bicycle codes, +where `a` and `b` are conjugate to each other. +The order of the cyclic group is `l`, and the shifts `a_shifts` and `b_shifts` are reverse to each other. + +See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref). """ function bicycle_codes(a_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) From 866ef37fd3323f0dafd3f8613e68dbcd16dd919a Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 00:20:14 +0900 Subject: [PATCH 45/67] import DocStringExtensions --- ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 6d5c46771..4a86eab8d 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -1,5 +1,7 @@ module QuantumCliffordHeckeExt +using DocStringExtensions + import QuantumClifford, LinearAlgebra import AbstractAlgebra: Group, GroupElem, AdditiveGroup, AdditiveGroupElem import Hecke: GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, From 58c3b0eaa345d8b5f47dabda33c2cb7bc62315a1 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 00:23:31 +0900 Subject: [PATCH 46/67] add examples --- ext/QuantumCliffordHeckeExt/lifted_product.jl | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 50dd35a51..21ae81e7b 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -18,6 +18,54 @@ The parity check matrix is obtained by applying `repr` to each element of the ma 4. Two base matrices of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. +## Code instances + +A [[882, 24, d ≤ 24]] code from https://arxiv.org/abs/2202.01702v3, following the 1st constructor. +During the construction, we do arithmetic operations to get the permutation group ring elements +with `x` being the offset-1 cyclic permutation and `GA(1)` being the unit. + +```jldoctest +julia> ENV["HECKE_PRINT_BANNER"] = "false"; import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra; + +julia> l = 63; GA = group_algebra(GF(2), abelian_group(l)); x = gens(GA)[]; + +julia> A = zeros(GA, 7, 7); + +julia> A[LinearAlgebra.diagind(A)] .= x^27; + +julia> A[LinearAlgebra.diagind(A, -1)] .= x^54; + +julia> A[LinearAlgebra.diagind(A, 6)] .= x^54; + +julia> A[LinearAlgebra.diagind(A, -2)] .= GA(1); + +julia> A[LinearAlgebra.diagind(A, 5)] .= GA(1); + +julia> B = reshape([1 + x + x^6], (1, 1)); + +julia> c1 = LPCode(A, B); + +julia> code_n(c1), code_k(c1) +(882, 24) +``` + +A [[175, 19, d ≤ 0]] code from paper http://arxiv.org/abs/2111.07029, following the 4th constructor. + +```jldoctest +julia> base_matrix = [0 0 0 0; 0 1 2 5; 0 6 3 1]; l = 7; + +julia> c2 = LPCode(base_matrix, l .- base_matrix', l); + +julia> code_n(c2), code_k(c2) +(175, 19) +``` + +## Examples of code subfamilies + +- When the base matrices of the `LPCode` are one-by-one, the code is called a two-block group-algebra code [`two_block_group_algebra_codes`](@ref). +- When the base matrices of the `LPCode` are one-by-one and their elements are sums of cyclic permuatations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). +- When the two matrices are adjoint to each other, the code is called a bicycle code [`bicycle_codes`](@ref). + ## The representation function In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. @@ -101,6 +149,13 @@ Here the group is choosen as the cyclic group of order `l`, and the base matrices `a` and `b` are the sum of the group algebra elements corresponding to the shifts `a_shifts` and `b_shifts`. See also: [`two_block_group_algebra_codes`](@ref), [`bicycle_codes`](@ref). + +```jldoctest +julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127); + +julia> code_n(c), code_k(c) +(254, 28) +``` """ function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) From 3329c8cb86e3686a40d250f7a8785e7306f378af Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 00:35:32 +0900 Subject: [PATCH 47/67] update docs and citations --- docs/src/references.bib | 23 +++++++++++++++++++ ext/QuantumCliffordHeckeExt/lifted.jl | 2 +- ext/QuantumCliffordHeckeExt/lifted_product.jl | 12 ++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/src/references.bib b/docs/src/references.bib index 9593ec285..1cbde2910 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -431,3 +431,26 @@ @inproceedings{panteleev2022asymptotically doi = {10.1145/3519935.3520017}, isbn = {978-1-4503-9264-8} } + +@article{roffe2023bias, + title = {Bias-Tailored Quantum {{LDPC}} Codes}, + author = {Roffe, Joschka and Cohen, Lawrence Z. and Quintavalle, Armanda O. and Chandra, Daryus and Campbell, Earl T.}, + year = {2023}, + month = may, + journal = {Quantum}, + volume = {7}, + pages = {1005}, + doi = {10.22331/q-2023-05-15-1005} +} + +@article{raveendran2022finite, + title = {Finite {{Rate QLDPC-GKP Coding Scheme}} That {{Surpasses}} the {{CSS Hamming Bound}}}, + author = {Raveendran, Nithin and Rengaswamy, Narayanan and Rozp{\k e}dek, Filip and Raina, Ankur and Jiang, Liang and Vasi{\'c}, Bane}, + year = {2022}, + month = jul, + journal = {Quantum}, + volume = {6}, + pages = {767}, + issn = {2521-327X}, + doi = {10.22331/q-2022-07-20-767}, +} diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 7c9c96c02..9bfbfa866 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -1,7 +1,7 @@ """ $TYPEDEF -Classical codes lifted over a group algebra [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). +Classical codes lifted over a group algebra, used for lifted product code construction [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 21ae81e7b..a9b3daa12 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -20,9 +20,10 @@ The parity check matrix is obtained by applying `repr` to each element of the ma ## Code instances -A [[882, 24, d ≤ 24]] code from https://arxiv.org/abs/2202.01702v3, following the 1st constructor. -During the construction, we do arithmetic operations to get the permutation group ring elements -with `x` being the offset-1 cyclic permutation and `GA(1)` being the unit. +A [[882, 24, d ≤ 24]] code from Appendix B of [roffe2023bias](@cite). +We use the the 1st constructor to generate the code and check its length and dimension. +During the construction, we do arithmetic operations to get the group algebra elements in base matrices `A` and `B`. +Here `x` is the generator of the group algebra, i.e., offset-1 cyclic permutation, and `GA(1)` is the unit element. ```jldoctest julia> ENV["HECKE_PRINT_BANNER"] = "false"; import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra; @@ -49,7 +50,8 @@ julia> code_n(c1), code_k(c1) (882, 24) ``` -A [[175, 19, d ≤ 0]] code from paper http://arxiv.org/abs/2111.07029, following the 4th constructor. +A [[175, 19, d ≤ 0]] code from Eq. (18) in Appendix A of [raveendran2022finite](@cite), +following the 4th constructor. ```jldoctest julia> base_matrix = [0 0 0 0; 0 1 2 5; 0 6 3 1]; l = 7; @@ -150,6 +152,8 @@ and the base matrices `a` and `b` are the sum of the group algebra elements corr See also: [`two_block_group_algebra_codes`](@ref), [`bicycle_codes`](@ref). +A [[254, 28, 14 ≤ d ≤ 20]] code from (A1) in Appendix B of [panteleev2021degenerate](@cite). + ```jldoctest julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127); From 14d70ef1f77333a30700cb1f0de670cb0f2d0214 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 00:39:14 +0900 Subject: [PATCH 48/67] update refs in ecc test base --- test/test_ecc_base.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 43aeda1f0..3df989383 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -19,7 +19,8 @@ random_circuit_code_args = vcat( ) # TODO benchmarking them in wiki -# test codes LP04 and LP118 are from https://arxiv.org/pdf/2111.07029 + +# test codes LP04 and LP118 from Appendix A of [raveendran2022finite](@cite), B04 = Dict( 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], @@ -36,8 +37,7 @@ B118 = Dict( LP04 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B04] LP118 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B118] -# generalized bicyle codes - +# generalized bicyle codes from from Appendix B of [panteleev2021degenerate](@cite). test_gb_codes = [ generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127), generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), @@ -45,7 +45,7 @@ test_gb_codes = [ other_lifted_product_codes = [] -# from https://arxiv.org/abs/2202.01702v3 +# from Eq. (18) in Appendix A of [raveendran2022finite](@cite) l = 63 GA = group_algebra(GF(2), abelian_group(l)) A = zeros(GA, 7, 7) @@ -55,9 +55,7 @@ A[LinearAlgebra.diagind(A, -1)] .= x^54 A[LinearAlgebra.diagind(A, 6)] .= x^54 A[LinearAlgebra.diagind(A, -2)] .= GA(1) A[LinearAlgebra.diagind(A, 5)] .= GA(1) - B = reshape([1 + x + x^6], (1, 1)) - push!(other_lifted_product_codes, LPCode(A, B)) const code_instance_args = Dict( From d624f14f15bbc175fa8f06dde0d08253c247ad1f Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 01:20:36 +0900 Subject: [PATCH 49/67] fix typos --- ext/QuantumCliffordHeckeExt/lifted_product.jl | 4 ++-- test/test_ecc_codeproperties.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index a9b3daa12..d914fcfe6 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -65,7 +65,7 @@ julia> code_n(c2), code_k(c2) ## Examples of code subfamilies - When the base matrices of the `LPCode` are one-by-one, the code is called a two-block group-algebra code [`two_block_group_algebra_codes`](@ref). -- When the base matrices of the `LPCode` are one-by-one and their elements are sums of cyclic permuatations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). +- When the base matrices of the `LPCode` are one-by-one and their elements are sums of cyclic permutations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). - When the two matrices are adjoint to each other, the code is called a bicycle code [`bicycle_codes`](@ref). ## The representation function @@ -147,7 +147,7 @@ end """ Generalized bicycle codes, which are a special case of 2GBA codes (and therefore of lifted product codes). -Here the group is choosen as the cyclic group of order `l`, +Here the group is chosen as the cyclic group of order `l`, and the base matrices `a` and `b` are the sum of the group algebra elements corresponding to the shifts `a_shifts` and `b_shifts`. See also: [`two_block_group_algebra_codes`](@ref), [`bicycle_codes`](@ref). diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index e916c0447..6fb8724e5 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -30,7 +30,7 @@ H = parity_checks(code) @test nqubits(code) == size(H, 2) == code_n(code) @test size(H, 1) == code_s(code) - @test code_s(code) + code_k(code) >= code_n(code) # possbly exist redundant checks + @test code_s(code) + code_k(code) >= code_n(code) # possibly exist redundant checks @test size(H, 1) <= size(H, 2) # equal in some cases with redundancy @test QuantumClifford.stab_looks_good(copy(H)) end From 3ae024752fcc0958c22a2b34137ead8b89a8d77b Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 01:20:59 +0900 Subject: [PATCH 50/67] add Hecke to test deps --- test/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Project.toml b/test/Project.toml index 67977b460..cd15ae310 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,6 +7,7 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" HostCPUFeatures = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" ILog2 = "2cd5bd5f-40a1-5050-9e10-fc8cdb6109f5" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" From 85a729307df11ea7205196f9d7cb5b2002adb55f Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 02:07:37 +0900 Subject: [PATCH 51/67] downgrade AbstractAlgebra and Hecke --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 3db9c6d23..639809d72 100644 --- a/Project.toml +++ b/Project.toml @@ -44,13 +44,13 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase" QuantumCliffordQuantikzExt = "Quantikz" [compat] -AbstractAlgebra = "0.42" +AbstractAlgebra = "0.39" CUDA = "4.4.0, 5" Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" -Hecke = "0.33" +Hecke = "0.28" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" From 4d35d69f19d72e325be27639df229ddbea41db92 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 09:16:22 +0900 Subject: [PATCH 52/67] adjust deps compat --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 639809d72..add7ec403 100644 --- a/Project.toml +++ b/Project.toml @@ -44,13 +44,13 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase" QuantumCliffordQuantikzExt = "Quantikz" [compat] -AbstractAlgebra = "0.39" +AbstractAlgebra = "0.39, 0.40, 0.41, 0.42" CUDA = "4.4.0, 5" Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" -Hecke = "0.28" +Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" From 72fa9fa52c839d95aae80c048caab9f1b5f480f5 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 09:41:23 +0900 Subject: [PATCH 53/67] Set compact according the Hecke v0.28 --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index add7ec403..0c7a0674f 100644 --- a/Project.toml +++ b/Project.toml @@ -44,7 +44,7 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase" QuantumCliffordQuantikzExt = "Quantikz" [compat] -AbstractAlgebra = "0.39, 0.40, 0.41, 0.42" +AbstractAlgebra = "^0.39.0, 0.40, 0.41, 0.42" CUDA = "4.4.0, 5" Combinatorics = "1.0" DataStructures = "0.18" @@ -58,7 +58,7 @@ LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" Makie = "0.20, 0.21" -Nemo = "0.42, 0.43, 0.44, 0.45, 0.46" +Nemo = "^0.42.1, 0.43, 0.44, 0.45, 0.46" Plots = "1.38.0" PrecompileTools = "1.2" PyQDecoders = "0.2.1" From 6c2f755cf59a5f79720fa5aa1f4b0ffe00924da8 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Tue, 10 Sep 2024 10:12:50 +0900 Subject: [PATCH 54/67] ignore AbstractAlgebra and Hecke in JET checks --- test/test_jet.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_jet.jl b/test/test_jet.jl index ca8e8d9b1..4e082dd88 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -5,6 +5,8 @@ using Static using Graphs using StridedViews using LinearAlgebra +using AbstractAlgebra, Hecke + using JET: ReportPass, BasicPass, InferenceErrorReport, UncaughtExceptionReport @@ -31,6 +33,8 @@ end AnyFrameModule(Static), AnyFrameModule(StridedViews), AnyFrameModule(LinearAlgebra), + AnyFrameModule(AbstractAlgebra), + AnyFrameModule(Hecke), ) ) @show rep From b73dcf7359de431957a4b222cb3dd3ee8b559823 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Sun, 15 Sep 2024 10:15:22 +0900 Subject: [PATCH 55/67] make AbstractAlgebra an explicit dep --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 96f223cb7..e837fa5a3 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Stefan Krastanov and QuantumSavory community version = "0.9.9" [deps] +AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -23,7 +24,6 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" [weakdeps] -AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" LDPCDecoders = "3c486d74-64b9-4c60-8b1a-13a564e77efb" @@ -35,7 +35,7 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" [extensions] QuantumCliffordGPUExt = "CUDA" -QuantumCliffordHeckeExt = ["AbstractAlgebra", "Hecke"] +QuantumCliffordHeckeExt = "Hecke" QuantumCliffordLDPCDecodersExt = "LDPCDecoders" QuantumCliffordMakieExt = "Makie" QuantumCliffordPlotsExt = "Plots" From e24a19b8b537ecd6a412482d9f9c6817b76a11c2 Mon Sep 17 00:00:00 2001 From: Yuxuan Date: Sun, 15 Sep 2024 10:27:22 +0900 Subject: [PATCH 56/67] remove ext checks of some functions for code construction --- .../QuantumCliffordHeckeExt.jl | 3 ++- src/ecc/codes/lifted_product.jl | 24 +++---------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 4a86eab8d..1dc7342fe 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -10,7 +10,8 @@ import Base: adjoint import Nemo: characteristic, lift, matrix_repr, GF, ZZ import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, - hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz + hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, + two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes include("types.jl") include("lifted.jl") diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index c695f9d67..ba07408aa 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -6,26 +6,8 @@ function LPCode(args...; kwargs...) return ext.LPCode(args...; kwargs...) end -function two_block_group_algebra_codes(args...; kwargs...) - ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) - if isnothing(ext) - throw("The `two_block_group_algebra_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `two_block_group_algebra_codes` will be available.") - end - return ext.two_block_group_algebra_codes(args...; kwargs...) -end +function two_block_group_algebra_codes end -function generalized_bicycle_codes(args...; kwargs...) - ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) - if isnothing(ext) - throw("The `generalized_bicycle_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `generalized_bicycle_codes` will be available.") - end - return ext.generalized_bicycle_codes(args...; kwargs...) -end +function generalized_bicycle_codes end -function bicycle_codes(args...; kwargs...) - ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) - if isnothing(ext) - throw("The `bicycle_codes` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `bicycle_codes` will be available.") - end - return ext.bicycle_codes(args...; kwargs...) -end +function bicycle_codes end From 17f6ca5d4b5ff3a9553a4e4a611ef98db0a5a16a Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 14:21:58 -0400 Subject: [PATCH 57/67] clarification about code_s Co-authored-by: hanakl --- CHANGELOG.md | 4 ++++ src/ecc/ECC.jl | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee7c838d..762c48595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - **(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.10 - 2024-09-26 + +- **(fix)** `ECC.code_s` now gives the number of parity checks with redundancy. If you want the number of linearly independent parity checks, you can use `LinearAlgebra.rank`. + ## v0.9.9 - 2024-08-05 - `inv` is implemented for all Clifford operator types (symbolic, dense, sparse). diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 549b545b7..d8e863136 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -88,14 +88,10 @@ code_n(c::AbstractECC) = code_n(parity_checks(c)) code_n(s::Stabilizer) = nqubits(s) -"""The number of stabilizer checks in a code.""" +"""The number of stabilizer checks in a code. They might not be all linearly independent, thus `code_s >= code_n-code_k`. For the number of linearly independent checks you can use `LinearAlgebra.rank`.""" function code_s end code_s(s::Stabilizer) = length(s) code_s(c::AbstractECC) = code_s(parity_checks(c)) -# function code_s(s::Stabilizer) -# _, _, r = canonicalize!(Base.copy(s), ranks=true) -# return r -# end """ The number of logical qubits in a code. From 014a77abb59ce951f31ced53f7d3e04db9edc872 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 14:25:32 -0400 Subject: [PATCH 58/67] reword one of the tests for rank/code_s consistency --- test/test_ecc_codeproperties.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index 6fb8724e5..523606422 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -31,7 +31,8 @@ @test nqubits(code) == size(H, 2) == code_n(code) @test size(H, 1) == code_s(code) @test code_s(code) + code_k(code) >= code_n(code) # possibly exist redundant checks - @test size(H, 1) <= size(H, 2) # equal in some cases with redundancy + _, _, rank = canonicalize!(copy(H), ranks=true) + @test rank <= size(H, 1) @test QuantumClifford.stab_looks_good(copy(H)) end end From a803298d788feac384dd873732134cb95e39fde7 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 14:35:45 -0400 Subject: [PATCH 59/67] make optional and disable the change to `stab_looks_good` --- src/QuantumClifford.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index dfbe2e522..ffe679a0d 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -1101,10 +1101,14 @@ end """ Check basic consistency requirements of a stabilizer. Used in tests. """ -function stab_looks_good(s) +function stab_looks_good(s; remove_redundant_rows=false) # first remove redundant rows - _, _, rank = canonicalize!(copy(s), ranks=true) - c = tab(s[1:rank]) + c = if remove_redundant_rows + s1, _, rank = canonicalize!(copy(s), ranks=true) + tab(s1[1:rank]) + else + tab(canonicalize!(copy(s))) + end nrows, ncols = size(c) all((c.phases .== 0x0) .| (c.phases .== 0x2)) || return false H = stab_to_gf2(c) From 5cd91a8362f9164c6b2cb852ebfb5cd2ac560f63 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:08:35 -0400 Subject: [PATCH 60/67] remove unnecessary dependency on AbstractAlgebra as Hecke re-exports all that is needed --- Project.toml | 2 -- ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl | 6 +++--- src/ecc/codes/classical/lifted.jl | 2 +- src/ecc/codes/lifted_product.jl | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index e837fa5a3..802c43685 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,6 @@ authors = ["Stefan Krastanov and QuantumSavory community version = "0.9.9" [deps] -AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -44,7 +43,6 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase" QuantumCliffordQuantikzExt = "Quantikz" [compat] -AbstractAlgebra = "^0.39.0, 0.40, 0.41, 0.42" CUDA = "4.4.0, 5" Combinatorics = "1.0" DataStructures = "0.18" diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 1dc7342fe..bd3d95869 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -3,8 +3,8 @@ module QuantumCliffordHeckeExt using DocStringExtensions import QuantumClifford, LinearAlgebra -import AbstractAlgebra: Group, GroupElem, AdditiveGroup, AdditiveGroupElem -import Hecke: GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, +import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, + GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, multiplication_table, coefficients, abelian_group, group_algebra import Base: adjoint import Nemo: characteristic, lift, matrix_repr, GF, ZZ @@ -17,4 +17,4 @@ include("types.jl") include("lifted.jl") include("lifted_product.jl") -end # module \ No newline at end of file +end # module diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index d7a9b1d40..c4e6e6e45 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,7 +1,7 @@ function LiftedCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext) - throw("The `LiftedCode` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `LiftedCode` will be available.") + throw("The `LiftedCode` depends on the package `Hecke` but you have not installed or imported it yet. Immediately after you import `Hecke`, the `LiftedCode` will be available.") end return ext.LiftedCode(args...; kwargs...) end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index ba07408aa..1eaf2de6b 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,7 +1,7 @@ function LPCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext) - throw("The `LPCode` depends on the package `AbstractAlgebra` and `Hecke` but you have not installed or imported them yet. Immediately after you import `AbstractAlgebra` and `Hecke`, the `LPCode` will be available.") + throw("The `LPCode` depends on the package `Hecke` but you have not installed or imported it yet. Immediately after you import `Hecke`, the `LPCode` will be available.") end return ext.LPCode(args...; kwargs...) end From 6f330245dd40d4595f76ece0cbbda375a2d65a92 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:28:04 -0400 Subject: [PATCH 61/67] do not pirate Nemo.lift, just define a new private lift function --- ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index bd3d95869..0b80cd0ff 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -7,7 +7,7 @@ import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, multiplication_table, coefficients, abelian_group, group_algebra import Base: adjoint -import Nemo: characteristic, lift, matrix_repr, GF, ZZ +import Nemo: characteristic, matrix_repr, GF, ZZ import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, From aaa824a84c08952f9af439689162f1957854a9e0 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:33:53 -0400 Subject: [PATCH 62/67] do not pirate Base.adjoint -- this actually does not seem used at all, should we delete it? --- ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl | 1 - ext/QuantumCliffordHeckeExt/types.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 0b80cd0ff..354de8a69 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -6,7 +6,6 @@ import QuantumClifford, LinearAlgebra import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, multiplication_table, coefficients, abelian_group, group_algebra -import Base: adjoint import Nemo: characteristic, matrix_repr, GF, ZZ import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl index 667a89b68..52ccb70dd 100644 --- a/ext/QuantumCliffordHeckeExt/types.jl +++ b/ext/QuantumCliffordHeckeExt/types.jl @@ -15,7 +15,7 @@ Compute the adjoint of a group algebra element. The adjoint is defined as the conjugate of the element in the group algebra, i.e. the inverse of the element in the associated group. """ -function adjoint(a::GroupAlgebraElem{T}) where T +function _adjoint(a::GroupAlgebraElem{T}) where T # TODO Is this used? Should it be deleted? A = parent(a) d = dim(A) v = Vector{T}(undef, d) From 162f8268b6e111105a0e94bd1af7956bdc4ff0b7 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:36:47 -0400 Subject: [PATCH 63/67] use references only to representable sources, not discussion pages that might disappear --- ext/QuantumCliffordHeckeExt/lifted.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 9bfbfa866..6ee83bd90 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -28,9 +28,11 @@ The default representation, provided by `Hecke`, is the permutation representati We also accept a custom representation function. Such a customization would be useful to reduce the number of bits required by the code construction. -For example, if we use a D4 group for lifting. In our default way, the representation will be `8×8` matrices, -where 8 is the group's order. We can find a `4×4` matrix representation for the group, -with details in [this discussion](https://github.com/QuantumSavory/QuantumClifford.jl/pull/312#discussion_r1682721136). +For example, if we use a D4 group for lifting, our default representation will be `8×8` permutation matrices, +where 8 is the group's order. +However, we can find a `4×4` matrix representation for the group, +e.g. by using the typical [`2×2` representation](https://en.wikipedia.org/wiki/Dihedral_group) +and converting it into binary representation by replacing "1" with the Pauli I, and "-1" with the Pauli X matrix. See also: [`LPCode`](@ref). From 8277803a073ffdc7cb9721d2187368b6dda6cdd6 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:46:45 -0400 Subject: [PATCH 64/67] missing documentation TODOs and minor rewording --- ext/QuantumCliffordHeckeExt/lifted.jl | 12 ++--- ext/QuantumCliffordHeckeExt/lifted_product.jl | 44 +++++++++++-------- src/ecc/codes/classical/lifted.jl | 3 ++ src/ecc/codes/lifted_product.jl | 6 +++ 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 6ee83bd90..6601ce236 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -1,7 +1,7 @@ """ $TYPEDEF -Classical codes lifted over a group algebra, used for lifted product code construction [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite). +Classical codes lifted over a group algebra, used for lifted product code construction ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) The parity-check matrix is constructed by applying `repr` to each element of `A`, which is mathematically a linear map from a group algebra element to a binary matrix. @@ -18,9 +18,9 @@ A lifted code can be constructed via the following approaches: 3. A matrix of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. -The default `GA` is the group algebra of `A[1, 1]`, the default representation is the permutation representation. +The default `GA` is the group algebra of `A[1, 1]`, the default representation `repr` is the permutation representation. -## The representation function +## The representation function `repr` In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. The default representation, provided by `Hecke`, is the permutation representation. @@ -57,13 +57,14 @@ end default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) """ -The GroupAlgebraElem with `GF(2)` coefficients can be converted to a permutation matrix by `representation_matrix` provided by Hecke. -""" +`LiftedCode` constructor using the default `GF(2)` representation (coefficients converted to a permutation matrix by `representation_matrix` provided by Hecke). +""" # TODO doctest example function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") LiftedCode(A; GA=GA, repr=default_repr) end +# TODO document and doctest example function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array[1,1])), repr::Union{Function, Nothing}=nothing) A = zeros(GA, size(group_elem_array)...) for i in axes(group_elem_array, 1), j in axes(group_elem_array, 2) @@ -76,6 +77,7 @@ function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::G end end +# TODO document and doctest example function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) A = zeros(GA, size(shift_array)...) for i in 1:size(shift_array, 1) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index d914fcfe6..b6ed74874 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -1,46 +1,49 @@ """ $TYPEDEF -Lifted product codes [panteleev2021degenerate](@cite) [panteleev2022asymptotically](@cite) +Lifted product codes ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) A lifted product code is defined by the hypergraph product of a base matrices `A` and the conjugate of another base matrix `B'`. Here, the hypergraph product is taken over a group algebra, of which the base matrices are consisting. -The parity check matrix is obtained by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from each group algebra element to a binary matrix. +The binary parity check matrix is obtained by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from each group algebra element to a binary matrix. ## Constructors +Multiple constructors are available: + 1. Two base matrices of group algebra elements. -2. Two lifted codes, whose base matrices are for quantum code construction. +2. Two lifted codes, whose base matrices are for quantum code construction. 3. Two base matrices of group elements, where each group element will be considered as a group algebra element by assigning a unit coefficient. 4. Two base matrices of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. -## Code instances +## Examples A [[882, 24, d ≤ 24]] code from Appendix B of [roffe2023bias](@cite). -We use the the 1st constructor to generate the code and check its length and dimension. +We use the 1st constructor to generate the code and check its length and dimension. During the construction, we do arithmetic operations to get the group algebra elements in base matrices `A` and `B`. Here `x` is the generator of the group algebra, i.e., offset-1 cyclic permutation, and `GA(1)` is the unit element. ```jldoctest -julia> ENV["HECKE_PRINT_BANNER"] = "false"; import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra; +julia> ENV["HECKE_PRINT_BANNER"] = "false"; # hide +julia> import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra: diagind; julia> l = 63; GA = group_algebra(GF(2), abelian_group(l)); x = gens(GA)[]; julia> A = zeros(GA, 7, 7); -julia> A[LinearAlgebra.diagind(A)] .= x^27; +julia> A[diagind(A)] .= x^27; -julia> A[LinearAlgebra.diagind(A, -1)] .= x^54; +julia> A[diagind(A, -1)] .= x^54; -julia> A[LinearAlgebra.diagind(A, 6)] .= x^54; +julia> A[diagind(A, 6)] .= x^54; -julia> A[LinearAlgebra.diagind(A, -2)] .= GA(1); +julia> A[diagind(A, -2)] .= GA(1); -julia> A[LinearAlgebra.diagind(A, 5)] .= GA(1); +julia> A[diagind(A, 5)] .= GA(1); julia> B = reshape([1 + x + x^6], (1, 1)); @@ -49,7 +52,7 @@ julia> c1 = LPCode(A, B); julia> code_n(c1), code_k(c1) (882, 24) ``` - + A [[175, 19, d ≤ 0]] code from Eq. (18) in Appendix A of [raveendran2022finite](@cite), following the 4th constructor. @@ -62,10 +65,10 @@ julia> code_n(c2), code_k(c2) (175, 19) ``` -## Examples of code subfamilies +## Code subfamilies and convenience constructors for them -- When the base matrices of the `LPCode` are one-by-one, the code is called a two-block group-algebra code [`two_block_group_algebra_codes`](@ref). -- When the base matrices of the `LPCode` are one-by-one and their elements are sums of cyclic permutations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). +- When the base matrices of the `LPCode` are 1×1, the code is called a two-block group-algebra code [`two_block_group_algebra_codes`](@ref). +- When the base matrices of the `LPCode` are 1×1 and their elements are sums of cyclic permutations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). - When the two matrices are adjoint to each other, the code is called a bicycle code [`bicycle_codes`](@ref). ## The representation function @@ -73,7 +76,7 @@ julia> code_n(c2), code_k(c2) In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. The default representation, provided by `Hecke`, is the permutation representation. -We also accept a custom representation function. The reasons are detailed in [`LiftedCode`](@ref). +We also accept a custom representation function as detailed in [`LiftedCode`](@ref). See also: [`LiftedCode`](@ref), [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). @@ -103,14 +106,17 @@ struct LPCode <: AbstractECC end end +# TODO document and doctest example function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) end +# TODO document and doctest example function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) end +# TODO document and doctest example function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) end @@ -138,7 +144,7 @@ Two-block group algebra (2GBA) codes, which are a special case of lifted product from two group algebra elements `a` and `b`, used as `1x1` base matrices. See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref) -""" +""" # TODO doctest example function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) A = reshape([a], (1, 1)) B = reshape([b], (1, 1)) @@ -160,7 +166,7 @@ julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], julia> code_n(c), code_k(c) (254, 28) ``` -""" +""" # TODO doctest example function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) a = sum(GA[n%l+1] for n in a_shifts) @@ -174,7 +180,7 @@ where `a` and `b` are conjugate to each other. The order of the cyclic group is `l`, and the shifts `a_shifts` and `b_shifts` are reverse to each other. See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref). -""" +""" # TODO doctest example function bicycle_codes(a_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) a = sum(GA[n÷l+1] for n in a_shifts) diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index c4e6e6e45..616d7be6f 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,3 +1,6 @@ +"""Classical codes lifted over a group algebra, used for lifted product code construction ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +Implemented as a package extension with Hecke. Check the QuantumClifford documentation for more details on that extension.""" function LiftedCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 1eaf2de6b..08cb24102 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,3 +1,6 @@ +"""Lifted product codes ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +Implemented as a package extension with Hecke. Check the QuantumClifford documentation for more details on that extension.""" function LPCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext) @@ -6,8 +9,11 @@ function LPCode(args...; kwargs...) return ext.LPCode(args...; kwargs...) end +"""Implemented in a package extension with Hecke.""" function two_block_group_algebra_codes end +"""Implemented in a package extension with Hecke.""" function generalized_bicycle_codes end +"""Implemented in a package extension with Hecke.""" function bicycle_codes end From 776aeac31d3c574759ba0fee57c20b96cf4d104a Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:49:10 -0400 Subject: [PATCH 65/67] remove repeated tests --- test/test_ecc_decoder_all_setups.jl | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index d8e8645af..4a8382c17 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -123,39 +123,3 @@ end end end end - -## - -using Test -using QuantumClifford -using QuantumClifford.ECC - -import PyQDecoders -import LDPCDecoders - -@testset "matching decoder, good as long as column weight of the code is limited" begin - codes = [ - Toric(8,8), - Toric(9,9), - Surface(8,8), - Surface(9,9) - ] - - noise = 0.01 - - setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] - - for c in codes - for s in setups - e = evaluate_decoder(PyMatchingDecoder(c), s, 10000) - #@show c - #@show s - #@show e - @assert max(e...) < noise/5 - end - end -end From 2404eb33ff21e63847525c72bcb1498fe755d4d7 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 15:49:49 -0400 Subject: [PATCH 66/67] remove a spurious todo --- test/test_ecc_base.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 3df989383..d9afc09bb 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -18,8 +18,6 @@ random_circuit_code_args = vcat( [map(f -> getfield(random_all_to_all_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_all_to_all_circuit_args] ) -# TODO benchmarking them in wiki - # test codes LP04 and LP118 from Appendix A of [raveendran2022finite](@cite), B04 = Dict( 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], From 7198b5b39ff550fb341b278f8c29653e340ee11b Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 16:19:27 -0400 Subject: [PATCH 67/67] make sure that the package extension content shows as part of the documentation --- docs/Project.toml | 1 + docs/make.jl | 8 +++++++- docs/src/ECC_API.md | 7 +++++++ ext/QuantumCliffordHeckeExt/lifted_product.jl | 1 - src/ecc/codes/classical/lifted.jl | 2 +- src/ecc/codes/lifted_product.jl | 2 +- 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index b414301a3..8ef02232a 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,6 +5,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" GraphMakie = "1ecd5474-83a3-4783-bb4f-06765db800d2" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" LDPCDecoders = "3c486d74-64b9-4c60-8b1a-13a564e77efb" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/docs/make.jl b/docs/make.jl index 699924cda..b8d13fc30 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,11 @@ using QuantumClifford using QuantumClifford.Experimental.NoisyCircuits using QuantumInterface +ENV["HECKE_PRINT_BANNER"] = "false" +import Hecke + +const QuantumCliffordHeckeExt = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + #DocMeta.setdocmeta!(QuantumClifford, :DocTestSetup, :(using QuantumClifford); recursive=true) ENV["LINES"] = 80 # for forcing `displaysize(io)` to be big enough @@ -20,8 +25,9 @@ doctest = false, clean = true, sitename = "QuantumClifford.jl", format = Documenter.HTML(size_threshold_ignore = ["API.md"]), -modules = [QuantumClifford, QuantumClifford.Experimental.NoisyCircuits, QuantumClifford.ECC, QuantumInterface], +modules = [QuantumClifford, QuantumClifford.Experimental.NoisyCircuits, QuantumClifford.ECC, QuantumInterface, QuantumCliffordHeckeExt], warnonly = [:missing_docs], +linkcheck = true, authors = "Stefan Krastanov", pages = [ "QuantumClifford.jl" => "index.md", diff --git a/docs/src/ECC_API.md b/docs/src/ECC_API.md index d77b26097..c400a2bf4 100644 --- a/docs/src/ECC_API.md +++ b/docs/src/ECC_API.md @@ -4,3 +4,10 @@ Modules = [QuantumClifford.ECC] Private = false ``` + +## Implemented in an extension requiring `Hecke.jl` + +```@autodocs +Modules = [QuantumCliffordHeckeExt] +Private = true +``` \ No newline at end of file diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index b6ed74874..aecad36b3 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -28,7 +28,6 @@ During the construction, we do arithmetic operations to get the group algebra el Here `x` is the generator of the group algebra, i.e., offset-1 cyclic permutation, and `GA(1)` is the unit element. ```jldoctest -julia> ENV["HECKE_PRINT_BANNER"] = "false"; # hide julia> import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra: diagind; julia> l = 63; GA = group_algebra(GF(2), abelian_group(l)); x = gens(GA)[]; diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl index 616d7be6f..a7c20fa04 100644 --- a/src/ecc/codes/classical/lifted.jl +++ b/src/ecc/codes/classical/lifted.jl @@ -1,6 +1,6 @@ """Classical codes lifted over a group algebra, used for lifted product code construction ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) -Implemented as a package extension with Hecke. Check the QuantumClifford documentation for more details on that extension.""" +Implemented as a package extension with Hecke. Check the [QuantumClifford documentation](http://qc.quantumsavory.org/stable/ECC_API/) for more details on that extension.""" function LiftedCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext) diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 08cb24102..338880702 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -1,6 +1,6 @@ """Lifted product codes ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) -Implemented as a package extension with Hecke. Check the QuantumClifford documentation for more details on that extension.""" +Implemented as a package extension with Hecke. Check the [QuantumClifford documentation](http://qc.quantumsavory.org/stable/ECC_API/) for more details on that extension.""" function LPCode(args...; kwargs...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) if isnothing(ext)