diff --git a/CHANGELOG.md b/CHANGELOG.md index ca601fe8b..98f22db78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,9 @@ These changes affect internal implementation details - external packages should - `permutesystems` and `permutesystems!` are no implemented, deprecating `permute` and `permute!` +## Added +- BP-OTS (Belief Propagation with Oscillating Trapping Sets) decoder for quantum LDPC codes in the LDPCDecoders extension + ## v0.9.18 - 2025-02-19 - Fixes for rare crashes in the python BP decoders. diff --git a/Project.toml b/Project.toml index 6b312f508..7cd814290 100644 --- a/Project.toml +++ b/Project.toml @@ -73,7 +73,7 @@ ILog2 = "0.2.3, 1, 2" InteractiveUtils = "1.10" JuMP = "1.23" KernelAbstractions = "0.9.35" -LDPCDecoders = "0.3.2" +LDPCDecoders = "0.3.3" LinearAlgebra = "1.10" MacroTools = "0.5.9" Makie = "0.20, 0.21, 0.22, 0.23, 0.24" diff --git a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl index 71c7a78a8..3068fbd8c 100644 --- a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl +++ b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl @@ -31,6 +31,88 @@ struct BitFlipDecoder <: AbstractSyndromeDecoder # TODO all these decoders have bfdecoderz end +""" + BPOTSDecoder(code; errorrate=nothing, maxiter=nothing, T=9, C=2.0) + +BP-OTS (Belief Propagation with Oscillating Trapping Sets) decoder for quantum LDPC codes. +This decoder uses oscillation tracking and biasing to escape trapping sets. + +# Arguments +- `code`: A quantum code (e.g., Toric, Surface) +- `errorrate`: Physical error rate (depolarizing probability) +- `maxiter`: Maximum number of iterations (default: 200) +- `T`: Biasing period (default: 9) +- `C`: Bias constant (default: 2.0) + +# Reference +Chytas et al., "Enhanced Message-Passing Decoding of Degenerate Quantum Codes +Utilizing Trapping Set Dynamics", IEEE Communications Letters, 2024 +""" +struct BPOTSDecoder <: AbstractSyndromeDecoder + original_code + H::SparseMatrixCSC{Bool,Int} + faults_matrix::Matrix{Bool} + n::Int + s::Int + k::Int + cx::Int + cz::Int + bpots_x::LDPCDecoders.BPOTSDecoder + bpots_z::LDPCDecoders.BPOTSDecoder +end + +function BPOTSDecoder(c; errorrate=nothing, maxiter=nothing, T=9, C=2.0) + + Hx_raw = parity_checks_x(c) + Hz_raw = parity_checks_z(c) + H_raw = parity_checks(c) + + if H_raw isa Stabilizer + H_gf2 = stab_to_gf2(H_raw) + H = sparse(Bool.(H_gf2)) + else + H = sparse(Bool.(H_raw)) + end + + if Hx_raw isa Stabilizer + Hx_gf2 = stab_to_gf2(Hx_raw) + Hz_gf2 = stab_to_gf2(Hz_raw) + Hx = sparse(Bool.(Hx_gf2)) + Hz = sparse(Bool.(Hz_gf2)) + else + Hx = sparse(Bool.(Hx_raw)) + Hz = sparse(Bool.(Hz_raw)) + end + + s, n = size(H) + k = code_k(c) + + cx = size(Hx, 1) + cz = size(Hz, 1) + + fm = BitMatrix(ones(Bool, s, 2*n)) + + errorrate = something(errorrate, 0.0) + maxiter = something(maxiter, 200) + bpots_x = LDPCDecoders.BPOTSDecoder(Hx, errorrate, maxiter; T=T, C=C) + bpots_z = LDPCDecoders.BPOTSDecoder(Hz, errorrate, maxiter; T=T, C=C) + + return BPOTSDecoder(c, H, fm, n, s, k, cx, cz, bpots_x, bpots_z) +end + +function decode(d::BPOTSDecoder, syndrome_sample::AbstractVector{Bool}) + length(syndrome_sample) == d.cx + d.cz || + throw(DimensionMismatch("Syndrome length ($(length(syndrome_sample))) does not match expected size ($(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, conv_z = LDPCDecoders.decode!(d.bpots_x, Vector(row_x)) + guess_x, conv_x = LDPCDecoders.decode!(d.bpots_z, Vector(row_z)) + + return vcat(guess_x, guess_z) +end + function BeliefPropDecoder(c; errorrate=nothing, maxiter=nothing) Hx = parity_matrix_x(c) Hz = parity_matrix_z(c) @@ -73,6 +155,7 @@ end parity_checks(d::BeliefPropDecoder) = d.H parity_checks(d::BitFlipDecoder) = d.H +parity_checks(d::BPOTSDecoder) = d.H function decode(d::BeliefPropDecoder, syndrome_sample) row_x = @view syndrome_sample[1:d.cx] diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 583b5b8f2..bb9329571 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -45,7 +45,7 @@ export parity_checks, parity_matrix_x, parity_matrix_z, iscss, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, TableDecoder, - BeliefPropDecoder, BitFlipDecoder, + BeliefPropDecoder, BitFlipDecoder, BPOTSDecoder, PyBeliefPropDecoder, PyBeliefPropOSDecoder, PyMatchingDecoder, DecoderCorrectionGate """Parity check tableau of a code. diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index 3572e450b..0b5b4dc05 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -287,6 +287,15 @@ function BitFlipDecoder(args...; kwargs...) return ext.BitFlipDecoder(args...; kwargs...) end +"""A BP-OTS (Belief Propagation with Oscillating Trapping Sets) decoder built around tools from `LDPCDecoders.jl`. +""" +function BPOTSDecoder(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordLDPCDecodersExt) + if isnothing(ext) + throw("The `BPOTSDecoder` depends on the package `LDPCDecoders` but you have not installed or imported `LDPCDecoders` yet. Immediately after you import `LDPCDecoders`, the `BPOTSDecoder` will be available.") + end + return ext.BPOTSDecoder(args...; kwargs...) +end """A Belief Propagation decoder built around tools from the python package `ldpc` available from the julia package `PyQDecoders.jl`.""" function PyBeliefPropDecoder(args...; kwargs...) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 7b7813dbc..fd5085c95 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -91,6 +91,36 @@ end end + ## + @testset "BPOTSDecoder decoder, good for topological codes" begin + codes = [ + Toric(6,6), + Toric(8,8), + Surface(6,6), + Surface(8,8) + ] + + noise = 0.01 + + setups = [ + CommutationCheckECCSetup(noise), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), + ] + + for c in codes + for s in setups + for d in [c->BPOTSDecoder(c, errorrate=noise, maxiter=200, T=9, C=2.0)] + e = evaluate_decoder(d(c), s, 10000) + #@show c + #@show s + #@show e + @test max(e...) < noise/2 + end + end + end + end + ## using Test