diff --git a/CHANGELOG.md b/CHANGELOG.md index 30cdbd09e..e3534ef23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ # News +## v0.9.11 + +- `hcat` of Tableaux objects +- `QuantumReedMuller` codes added to the ECC module +- **(breaking)** change the convention for how to provide a representation function in the constructor of `LPCode` -- strictly speaking a breaking change, but this is not an API that is publicly used in practice + ## v0.9.10 - 2024-09-26 - The lifted product class of quantum LDPC codes is implemented in the ECC submodule. diff --git a/Project.toml b/Project.toml index 02555d45f..d8f9e83e4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.10" +version = "0.9.11" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" @@ -48,7 +48,7 @@ Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" -Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33" +Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" @@ -56,7 +56,7 @@ LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" Makie = "0.20, 0.21" -Nemo = "^0.42.1, 0.43, 0.44, 0.45, 0.46" +Nemo = "0.42.1, 0.43, 0.44, 0.45, 0.46, 0.47" Plots = "1.38.0" PrecompileTools = "1.2" PyQDecoders = "0.2.1" diff --git a/docs/src/references.bib b/docs/src/references.bib index 1cbde2910..29500a034 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -454,3 +454,36 @@ @article{raveendran2022finite issn = {2521-327X}, doi = {10.22331/q-2022-07-20-767}, } + +@article{steane1999quantum, + title={Quantum reed-muller codes}, + author={Steane, Andrew M}, + journal={IEEE Transactions on Information Theory}, + volume={45}, + number={5}, + pages={1701--1703}, + year={1999}, + publisher={IEEE} +} + +@article{campbell2012magic, + title={Magic-state distillation in all prime dimensions using quantum reed-muller codes}, + author={Campbell, Earl T and Anwar, Hussain and Browne, Dan E}, + journal={Physical Review X}, + volume={2}, + number={4}, + pages={041021}, + year={2012}, + publisher={APS} +} + +@article{anderson2014fault, + title={Fault-tolerant conversion between the steane and reed-muller quantum codes}, + author={Anderson, Jonas T and Duclos-Cianci, Guillaume and Poulin, David}, + journal={Physical review letters}, + volume={113}, + number={8}, + pages={080501}, + year={2014}, + publisher={APS} +} diff --git a/docs/src/references.md b/docs/src/references.md index b37e94d4e..35e944a21 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -37,6 +37,9 @@ For quantum code construction routines: - [kitaev2003fault](@cite) - [fowler2012surface](@cite) - [knill1996concatenated](@cite) +- [steane1999quantum](@cite) +- [campbell2012magic](@cite) +- [anderson2014fault](@cite) For classical code construction routines: - [muller1954application](@cite) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 354de8a69..29e9de8ce 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -6,7 +6,8 @@ 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 Nemo: characteristic, matrix_repr, GF, ZZ +import Nemo +import Nemo: characteristic, matrix_repr, GF, ZZ, lift 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, diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 6601ce236..75964f983 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -22,10 +22,11 @@ The default `GA` is the group algebra of `A[1, 1]`, the default representation ` ## 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. +We use the default representation function `Hecke.representation_matrix` 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. +We also accept a custom representation function (the `repr` field of the constructor). +Whatever the representation, the matrix elements need to be convertible to Integers (e.g. permit `lift(ZZ, ...)`). 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, our default representation will be `8×8` permutation matrices, @@ -54,14 +55,12 @@ struct LiftedCode <: ClassicalCode end end -default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) - """ `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) + LiftedCode(A; GA=GA, repr=representation_matrix) end # TODO document and doctest example @@ -71,7 +70,7 @@ function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::G A[i, j] = GA[A[i, j]] end if repr === nothing - return LiftedCode(A; GA=GA, repr=default_repr) + return LiftedCode(A; GA=GA, repr=representation_matrix) else return LiftedCode(A; GA=GA, repr=repr) end @@ -85,11 +84,16 @@ function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_alg A[i, j] = GA[shift_array[i, j]%l+1] end end - return LiftedCode(A; GA=GA, repr=default_repr) + return LiftedCode(A; GA=GA, repr=representation_matrix) 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)]...) +lift_to_bool(x) = Bool(Int(lift(ZZ,x))) + +function concat_lift_repr(repr, mat) + x = repr.(mat) + y = hvcat(size(x,2), transpose(x)...) + z = Matrix(lift_to_bool.(y)) + return z end function parity_checks(c::LiftedCode) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index aecad36b3..97e41b044 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -72,7 +72,7 @@ julia> code_n(c2), code_k(c2) ## 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. +We use the default representation function `Hecke.representation_matrix` 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 as detailed in [`LiftedCode`](@ref). @@ -107,24 +107,24 @@ 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) + LPCode(LiftedCode(A; GA=GA, repr=representation_matrix), LiftedCode(B; GA=GA, repr=representation_matrix); GA=GA, repr=representation_matrix) 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) + LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=representation_matrix) 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) + LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=representation_matrix) 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) + hx, hz = concat_lift_repr(c.repr,hx), concat_lift_repr(c.repr,hz) return hx, hz end diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl index 52ccb70dd..dd3845acc 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 # TODO Is this used? Should it be deleted? +function Base.adjoint(a::GroupAlgebraElem{T}) where T # TODO we would like to use Base.adjoint, but that would be type piracy. Upstream this to Nemo or Hecke or AbstractAlgebra A = parent(a) d = dim(A) v = Vector{T}(undef, d) diff --git a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl index 8c369cf82..e80c13af6 100644 --- a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl +++ b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl @@ -74,16 +74,16 @@ parity_checks(d::BeliefPropDecoder) = d.H parity_checks(d::BitFlipDecoder) = d.H function decode(d::BeliefPropDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.cx] - row_z = syndrome_sample[d.cx+1:d.cx+d.cz] + row_x = @view syndrome_sample[1:d.cx] + row_z = @view syndrome_sample[d.cx+1:d.cx+d.cz] guess_z, success = LDPCDecoders.decode!(d.bpdecoderx, row_x) guess_x, success = LDPCDecoders.decode!(d.bpdecoderz, row_z) return vcat(guess_x, guess_z) end function decode(d::BitFlipDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.cx] - row_z = syndrome_sample[d.cx+1:d.cx+d.cz] + row_x = @view syndrome_sample[1:d.cx] + row_z = @view syndrome_sample[d.cx+1:d.cx+d.cz] guess_z, success = LDPCDecoders.decode!(d.bfdecoderx, row_x) guess_x, success = LDPCDecoders.decode!(d.bfdecoderz, row_z) return vcat(guess_x, guess_z) diff --git a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl index bfa557d05..e3496a733 100644 --- a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl +++ b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl @@ -69,8 +69,8 @@ end parity_checks(d::PyBP) = d.H function decode(d::PyBP, syndrome_sample) - row_x = syndrome_sample[1:d.nx] # TODO These copies and indirections might be costly! - row_z = syndrome_sample[d.nx+1:end] + row_x = @view syndrome_sample[1:d.nx] + row_z = @view syndrome_sample[d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode(np.array(row_x))) guess_x_errors = PythonCall.PyArray(d.pyz.decode(np.array(row_z))) vcat(guess_x_errors, guess_z_errors) @@ -106,18 +106,19 @@ end parity_checks(d::PyMatchingDecoder) = d.H function decode(d::PyMatchingDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.nx] # TODO This copy is costly! - row_z = syndrome_sample[d.nx+1:end] + row_x = @view syndrome_sample[1:d.nx] + row_z = @view syndrome_sample[d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode(row_x)) guess_x_errors = PythonCall.PyArray(d.pyz.decode(row_z)) vcat(guess_x_errors, guess_z_errors) end function batchdecode(d::PyMatchingDecoder, syndrome_samples) - row_x = syndrome_samples[:,1:d.nx] # TODO This copy is costly! - row_z = syndrome_samples[:,d.nx+1:end] + row_x = @view syndrome_samples[:,1:d.nx] + row_z = @view syndrome_samples[:,d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode_batch(row_x)) guess_x_errors = PythonCall.PyArray(d.pyz.decode_batch(row_z)) + n_cols_x = size(guess_x_errors, 2) hcat(guess_x_errors, guess_z_errors) end diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 7f50d4150..7c4e7f371 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -963,6 +963,19 @@ function check_allrowscommute(stabilizer::Tableau) end check_allrowscommute(stabilizer::Stabilizer)=check_allrowscommute(tab(stabilizer)) +""" +Vertically concatenates tableaux. + +```jldoctest +julia> vcat(ghz(2), ghz(2)) ++ XX ++ ZZ ++ XX ++ ZZ +``` + +See also: [`hcat`](@ref) +""" function Base.vcat(tabs::Tableau...) Tableau( vcat((s.phases for s in tabs)...), @@ -970,7 +983,41 @@ function Base.vcat(tabs::Tableau...) hcat((s.xzs for s in tabs)...)) end -Base.vcat(stabs::Stabilizer...) = Stabilizer(vcat((tab(s) for s in stabs)...)) +Base.vcat(stabs::Stabilizer{T}...) where {T} = Stabilizer(vcat((tab(s) for s in stabs)...)) + +""" +Horizontally concatenates tableaux. + +```jldoctest +julia> hcat(ghz(2), ghz(2)) ++ XXXX ++ ZZZZ +``` + +See also: [`vcat`](@ref) +""" +function Base.hcat(tabs::Tableau...) # TODO this implementation is slow as it unpacks each bitvector into bits and repacks them -- reuse the various tableau inset functionality we have to speed this up + rows = size(tabs[1], 1) + cols = sum(map(nqubits, tabs)) + newtab = zero(Tableau, rows, cols) + cols_idx = 1 + for tab in tabs + rows_tab, cols_tab = size(tab) + if rows_tab != rows + throw(ArgumentError("All input Tableaux/Stabilizers must have the same number of rows.")) + end + for i in 1:rows + for j in 1:cols_tab + newtab[i, cols_idx+j-1]::Tuple{Bool,Bool} = tab[i, j]::Tuple{Bool,Bool} + end + newtab.phases[i] = (newtab.phases[i]+tab.phases[i])%4 + end + cols_idx += cols_tab + end + return newtab +end + +Base.hcat(stabs::Stabilizer{T}...) where {T} = Stabilizer(hcat((tab(s) for s in stabs)...)) ############################## # Unitary Clifford Operations @@ -1017,6 +1064,16 @@ function _apply!(stab::AbstractStabilizer, p::PauliOperator, indices; phases::Va stab end +############################## +# Conversion and promotion +############################## + +Base.promote_rule(::Type{<:Destabilizer{T}} , ::Type{<:MixedDestabilizer{T}}) where {T<:Tableau} = MixedDestabilizer{T} +Base.promote_rule(::Type{<:MixedStabilizer{T}}, ::Type{<:MixedDestabilizer{T}}) where {T<:Tableau} = MixedDestabilizer{T} +Base.promote_rule(::Type{<:Stabilizer{T}} , ::Type{<:S} ) where {T<:Tableau, S<:Union{MixedStabilizer{T}, Destabilizer{T}, MixedDestabilizer{T}}} = S + +Base.convert(::Type{<:MixedDestabilizer{T}}, x::Union{Destabilizer{T}, MixedStabilizer{T}, Stabilizer{T}}) where {T <: Tableau} = MixedDestabilizer(x) + ############################## # Helpers for binary codes ############################## diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index d8e863136..cdda7742e 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -20,7 +20,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, - Toric, Gottesman, Surface, Concat, CircuitCode, + Toric, Gottesman, Surface, Concat, CircuitCode, QuantumReedMuller, LPCode, two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, @@ -376,10 +376,10 @@ include("codes/gottesman.jl") include("codes/surface.jl") include("codes/concat.jl") include("codes/random_circuit.jl") - include("codes/classical/reedmuller.jl") include("codes/classical/recursivereedmuller.jl") include("codes/classical/bch.jl") +include("codes/quantumreedmuller.jl") # qLDPC include("codes/classical/lifted.jl") diff --git a/src/ecc/codes/quantumreedmuller.jl b/src/ecc/codes/quantumreedmuller.jl new file mode 100644 index 000000000..6813d6646 --- /dev/null +++ b/src/ecc/codes/quantumreedmuller.jl @@ -0,0 +1,41 @@ +""" +The family of `[[2ᵐ - 1, 1, 3]]` CSS Quantum-Reed-Muller codes, as discovered by Steane in his 1999 paper [steane1999quantum](@cite). + +Quantum codes are constructed from shortened Reed-Muller codes `RM(1, m)`, by removing the first row and column of the generator matrix `Gₘ`. Similarly, we can define truncated dual codes `RM(m - 2, m)` using the generator matrix `Hₘ` [anderson2014fault](@cite). The quantum Reed-Muller codes `QRM(m)` derived from `RM(1, m)` are CSS codes. + +Given that the stabilizers of the quantum code are defined through the generator matrix of the classical code, the minimum distance of the quantum code corresponds to the minimum distance of the dual classical code, which is `d = 3`, thus it can correct any single qubit error. Since one stabilizer from the original and one from the dual code are removed in the truncation process, the code parameters are `[[2ᵐ - 1, 1, 3]]`. + +You might be interested in consulting [anderson2014fault](@cite) and [campbell2012magic](@cite) as well. + +The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/quantum_reed_muller). +""" +struct QuantumReedMuller <: AbstractECC + m::Int + function QuantumReedMuller(m) + if m < 3 + throw(DomainError("Invalid parameters: m must be bigger than 2 in order to have a valid code.")) + end + new(m) + end +end + +function iscss(::Type{QuantumReedMuller}) + return true +end + +function parity_checks(c::QuantumReedMuller) + RM₁₋ₘ = generator(RecursiveReedMuller(1, c.m)) + RM₍ₘ₋₂₎₋ₘ₎ = generator(RecursiveReedMuller(c.m-2, c.m)) + QRM = CSS(RM₁₋ₘ[2:end, 2:end], RM₍ₘ₋₂₎₋ₘ₎[2:end, 2:end]) + Stabilizer(QRM) +end + +code_n(c::QuantumReedMuller) = 2^c.m - 1 + +code_k(c::QuantumReedMuller) = 1 + +distance(c::QuantumReedMuller) = 3 + +parity_checks_x(c::QuantumReedMuller) = stab_to_gf2(parity_checks(QuantumReedMuller(c.m)))[1:c.m, 1:end÷2] + +parity_checks_z(c::QuantumReedMuller) = stab_to_gf2(parity_checks(QuantumReedMuller(c.m)))[end-(code_n(c::QuantumReedMuller)-2-c.m):end, end÷2+1:end] diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index 0488dd28a..3ac2680cf 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -171,12 +171,21 @@ end function evaluate_guesses(measured_faults, guesses, faults_matrix) nsamples = size(guesses, 1) - guess_faults = (faults_matrix * guesses') .% 2 # TODO this can be faster and non-allocating by turning it into a loop - decoded = 0 - for i in 1:nsamples # TODO this can be faster and non-allocating by having the loop and the matrix multiplication on the line above work together and not store anything - (@view guess_faults[:,i]) == (@view measured_faults[i,:]) && (decoded += 1) + fails = 0 + for i in 1:nsamples + for j in 1:size(faults_matrix, 1) + sum_mod = 0 + @inbounds @simd for k in 1:size(faults_matrix, 2) + sum_mod += faults_matrix[j, k] * guesses[i, k] + end + sum_mod %= 2 + if sum_mod != measured_faults[i, j] + fails += 1 + break + end + end end - return (nsamples - decoded) / nsamples + return fails / nsamples end function evaluate_decoder(d::AbstractSyndromeDecoder, setup::CommutationCheckECCSetup, nsamples::Int) diff --git a/src/linalg.jl b/src/linalg.jl index 16b4eaf52..b95fba888 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -161,8 +161,10 @@ julia> tensor(s, s) See also [`tensor_pow`](@ref).""" function tensor end -function tensor(ops::AbstractStabilizer...) # TODO optimize this by doing conversion to common type to enable preallocation - foldl(⊗, ops[2:end], init=ops[1]) +function tensor(ops::AbstractStabilizer...) # TODO optimize by pre-allocating one large tableau instead of the current quadratic fold + ct = promote_type(map(typeof, ops)...) + conv_ops = map(x -> convert(ct, x), ops) + return foldl(⊗, conv_ops) end """Repeated tensor product of an operators or a tableau. diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 88fb7d229..4420f9891 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -323,8 +323,8 @@ end @qubitop2 YCY (x1⊻z2⊻x2, z1⊻x2⊻z2 , x1⊻x2⊻z1 , x1⊻z1⊻z2, ~iszero( (x1 & ~z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2))) @qubitop2 YCZ (x1⊻x2 , x2⊻z1 , x2 , z2⊻x1⊻z1, ~iszero( (x2 & (x1 ⊻ z1) & (z2 ⊻ x1)) )) -@qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & ~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) -@qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 & ~x2 & ~z2) | (x1 & ~z1 & ~x2 & z2) | (x1 & z1 & ~x2 & z2) | (x1 & z1 & x2 & z2))) +@qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 &~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) +@qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 &~x2) | (x1 & z1 & z2) | (x1 &~x2 & z2))) #= To get the boolean formulas for the phase, it is easiest to first write down the truth table for the phase: diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index d9afc09bb..f087ea627 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -57,19 +57,20 @@ B = reshape([1 + x + x^6], (1, 1)) push!(other_lifted_product_codes, LPCode(A, B)) 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, - LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)) + :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, + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)), + :QuantumReedMuller => [3, 4, 5] ) function all_testablable_code_instances(;maxn=nothing) codeinstances = [] for t in subtypes(QuantumClifford.ECC.AbstractECC) - for c in get(code_instance_args, t, []) + for c in get(code_instance_args, t.name.name, []) codeinstance = t(c...) !isnothing(maxn) && nqubits(codeinstance) > maxn && continue push!(codeinstances, codeinstance) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index 523606422..ec529d3e4 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -33,7 +33,7 @@ @test code_s(code) + code_k(code) >= code_n(code) # possibly exist redundant checks _, _, rank = canonicalize!(copy(H), ranks=true) @test rank <= size(H, 1) - @test QuantumClifford.stab_looks_good(copy(H)) + @test QuantumClifford.stab_looks_good(copy(H), remove_redundant_rows=true) end end end diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 4a8382c17..c98813a38 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -26,7 +26,7 @@ #@show c #@show s #@show e - @assert max(e...) < noise/4 + @test max(e...) < noise/4 end end end @@ -35,8 +35,40 @@ ## @testset "belief prop decoders, good for sparse codes" begin + codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) + + noise = 0.001 + + setups = [ + CommutationCheckECCSetup(noise), + 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=2)] + nsamples = 10000 + if true + @test_broken false # TODO these are too slow to test in CI + continue + end + e = evaluate_decoder(d(c), s, nsamples) + # @show c + # @show s + # @show e + @test max(e...) <= noise + end + end + end + end + + + @testset "BitFlipDecoder decoder, good for sparse codes" begin codes = [ - # TODO + QuantumReedMuller(3), + QuantumReedMuller(4) ] noise = 0.001 @@ -49,12 +81,12 @@ for c in codes for s in setups - for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] + for d in [c->BitFlipDecoder(c, maxiter=10)] e = evaluate_decoder(d(c), s, 100000) - @show c - @show s - @show e - @assert max(e...) < noise/4 + #@show c + #@show s + #@show e + @test max(e...) < noise/4 end end end @@ -91,34 +123,7 @@ #@show c #@show s #@show e - @assert max(e...) < noise/5 - end - end - end -end - -@testset "belief prop decoders, good for sparse codes" begin - codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) - - noise = 0.001 - - setups = [ - CommutationCheckECCSetup(noise), - 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)] - 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) + @test max(e...) < noise/5 end end end diff --git a/test/test_ecc_quantumreedmuller.jl b/test/test_ecc_quantumreedmuller.jl new file mode 100644 index 000000000..efb76918f --- /dev/null +++ b/test/test_ecc_quantumreedmuller.jl @@ -0,0 +1,50 @@ +@testitem "Quantum Reed-Muller" begin + using Test + using Nemo: echelon_form, matrix, GF + using LinearAlgebra + using QuantumClifford + using QuantumClifford: canonicalize!, Stabilizer, stab_to_gf2 + using QuantumClifford.ECC + using QuantumClifford.ECC: AbstractECC, QuantumReedMuller, Steane7, CSS + + function designed_distance(mat) + dist = 3 + for row in eachrow(mat) + count = sum(row) + if count < dist + return false + end + end + return true + end + + @testset "Test QuantumReedMuller(r,m) properties" begin + for m in 3:10 + stab = parity_checks(QuantumReedMuller(m)) + H = stab_to_gf2(stab) + @test designed_distance(H) == true + # QuantumReedMuller(3) is the Steane7 code. + @test canonicalize!(parity_checks(Steane7())) == parity_checks(QuantumReedMuller(3)) + @test code_n(QuantumReedMuller(m)) == 2^m - 1 + @test code_k(QuantumReedMuller(m)) == 1 + @test distance(QuantumReedMuller(m)) == 3 + @test H == stab_to_gf2(parity_checks(CSS(parity_checks_x(QuantumReedMuller(m)), parity_checks_z(QuantumReedMuller(m))))) + # [[15,1,3]] qrm code from table 1 of https://arxiv.org/pdf/1705.0010 + qrm₁₅₁₃ = S"ZIZIZIZIZIZIZIZ + IZZIIZZIIZZIIZZ + IIIZZZZIIIIZZZZ + IIIIIIIZZZZZZZZ + IIZIIIZIIIZIIIZ + IIIIZIZIIIIIZIZ + IIIIIZZIIIIIIZZ + IIIIIIIIIZZIIZZ + IIIIIIIIIIIZZZZ + IIIIIIIIZIZIZIZ + XIXIXIXIXIXIXIX + IXXIIXXIIXXIIXX + IIIXXXXIIIIXXXX + IIIIIIIXXXXXXXX" + @test canonicalize!(parity_checks(qrm₁₅₁₃)) == canonicalize!(parity_checks(QuantumReedMuller(4))) + end + end +end diff --git a/test/test_jet.jl b/test/test_jet.jl index 1b8b01504..42454e54e 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -25,5 +25,5 @@ @show rep @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 11 + @test length(JET.get_reports(rep)) <= 10 end diff --git a/test/test_stabs.jl b/test/test_stabs.jl index 63a5c562e..b479934c2 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -55,6 +55,12 @@ stabs = [s[1:i] for s in [random_stabilizer(n) for n in [32,16,16,64,63,65,129,128,127]] for i in rand(1:10)]; mdstabs = MixedDestabilizer.(stabs); @test canonicalize!(⊗(stabs...)) == canonicalize!(stabilizerview(⊗(mdstabs...))) + md = MixedDestabilizer(random_destabilizer(n)) + s = random_stabilizer(n) + mds = md⊗s + @test mixed_destab_looks_good(mds) + estab = stabilizerview(md)⊗s + @test canonicalize!(copy(stabilizerview(mds))) == canonicalize!(estab) end end @@ -90,4 +96,15 @@ @test stab_to_gf2(s2a) == stab_to_gf2(s2b) end end + + @testset "horizontal concatenation" begin + @test hcat(ghz(2), ghz(2)) == S"XXXX ZZZZ" + s1 = S"YZ -XX" + s2 = S"-ZY -YX" + @test hcat(copy(s1), copy(s2)) == S"-YZZY XXYX" + @test hcat(copy(s1), copy(s2), copy(s1), copy(s2)) == S"YZZYYZZY XXYXXXYX" + @test_throws ArgumentError hcat(copy(s1), random_stabilizer(3)) + @test hcat(copy(tab(s1)), copy(tab(s2))) == T"-YZZY XXYX" + @test hcat(copy(tab(s1)), copy(tab(s2)), copy(tab(s1)), copy(tab(s2))) == T"YZZYYZZY XXYXXXYX" + end end