From 4a007f913a92e9d8bc934917498209c17fbd4f3f Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Mon, 8 Apr 2024 23:08:23 -0400 Subject: [PATCH] add iterative bitflip decoder (#255) --- CHANGELOG.md | 4 ++ Project.toml | 2 +- .../QuantumCliffordLDPCDecodersExt.jl | 71 +++++++++++++------ .../QuantumCliffordPyQDecodersExt.jl | 17 +++-- src/ecc/ECC.jl | 2 +- src/ecc/decoder_pipeline.jl | 10 +++ 6 files changed, 78 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d162719..2460e82e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.3 - 2024-04-08 + +- The ECC module now has access to an iterative bitflip decoder thanks to `LDPCDecoders.jl`. + ## v0.9.2 - 2024-04-02 - Provide more configuration options in the `PyBeliefProp` decoders. diff --git a/Project.toml b/Project.toml index e5aa2488c..c2b171e3f 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.2" +version = "0.9.3" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl index 7fff5d851..8c369cf82 100644 --- a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl +++ b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl @@ -6,28 +6,31 @@ using QuantumClifford using QuantumClifford.ECC import QuantumClifford.ECC: AbstractSyndromeDecoder, decode, parity_checks -struct BeliefPropDecoder <: AbstractSyndromeDecoder - """Stabilizer tableau defining the code""" +struct BeliefPropDecoder <: AbstractSyndromeDecoder # TODO all these decoders have the same fields, maybe we can factor out a common type H - """Faults matrix corresponding to the code""" faults_matrix - """The number of qubits in the code""" n - """The depth of the code""" s - """The number of encoded qubits""" k - """Number of X checks""" cx - """Number of X checks""" cz - """The classical BP decoder for Hx""" bpdecoderx - """The classical BP decoder for Hz""" bpdecoderz end -function BeliefPropDecoder(c, p_init=0, max_iters=10) +struct BitFlipDecoder <: AbstractSyndromeDecoder # TODO all these decoders have the same fields, maybe we can factor out a common type + H + faults_matrix + n + s + k + cx + cz + bfdecoderx + bfdecoderz +end + +function BeliefPropDecoder(c; errorrate=nothing, maxiter=nothing) Hx = parity_checks_x(c) Hz = parity_checks_z(c) H = parity_checks(c) @@ -35,27 +38,55 @@ function BeliefPropDecoder(c, p_init=0, max_iters=10) _, _, r = canonicalize!(Base.copy(H), ranks=true) k = n - r cx = size(Hx, 1) - cz = size(Hx, 1) + cz = size(Hz, 1) fm = faults_matrix(H) - bpx = LDPCDecoders.BeliefPropagationDecoder(Hx, p_init, max_iters) - bpz = LDPCDecoders.BeliefPropagationDecoder(Hz, p_init, max_iters) + isnothing(errorrate) || 0≤errorrate≤1 || error(lazy"BeliefPropDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].") + errorrate = isnothing(errorrate) ? 0.0 : errorrate + maxiter = isnothing(maxiter) ? n : maxiter + bpx = LDPCDecoders.BeliefPropagationDecoder(Hx, errorrate, maxiter) + bpz = LDPCDecoders.BeliefPropagationDecoder(Hz, errorrate, maxiter) return BeliefPropDecoder(H, fm, n, s, k, cx, cz, bpx, bpz) end +function BitFlipDecoder(c; errorrate=nothing, maxiter=nothing) + Hx = parity_checks_x(c) + Hz = parity_checks_z(c) + H = parity_checks(c) + s, n = size(H) + _, _, r = canonicalize!(Base.copy(H), ranks=true) + k = n - r + cx = size(Hx, 1) + cz = size(Hz, 1) + fm = faults_matrix(H) + + isnothing(errorrate) || 0≤errorrate≤1 || error(lazy"BitFlipDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].") + errorrate = isnothing(errorrate) ? 0.0 : errorrate + maxiter = isnothing(maxiter) ? n : maxiter + bfx = LDPCDecoders.BitFlipDecoder(Hx, errorrate, maxiter) + bfz = LDPCDecoders.BitFlipDecoder(Hz, errorrate, maxiter) + + return BitFlipDecoder(H, fm, n, s, k, cx, cz, bfx, bfz) +end + 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] + 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 - guess_x = falses(d.n) - guess_z = falses(d.n) - - success = LDPCDecoders.decode!(d.bpx, row_x, guess_x) - success = LDPCDecoders.decode!(d.bpz, row_z, guess_z) - return vcat(guess_z, guess_x) +function decode(d::BitFlipDecoder, syndrome_sample) + row_x = syndrome_sample[1:d.cx] + row_z = 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) end end diff --git a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl index c3ee574bc..332c28ac7 100644 --- a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl +++ b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl @@ -8,7 +8,7 @@ import QuantumClifford.ECC: AbstractSyndromeDecoder, decode, batchdecode, parity abstract type PyBP <: AbstractSyndromeDecoder end -struct PyBeliefPropDecoder <: PyBP +struct PyBeliefPropDecoder <: PyBP # TODO all these decoders have the same fields, maybe we can factor out a common type code H Hx @@ -20,7 +20,7 @@ struct PyBeliefPropDecoder <: PyBP pyz end -struct PyBeliefPropOSDecoder <: PyBP +struct PyBeliefPropOSDecoder <: PyBP # TODO all these decoders have the same fields, maybe we can factor out a common type code H Hx @@ -47,7 +47,7 @@ function PyBeliefPropDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=not return PyBeliefPropDecoder(c, H, Hx, Hz, size(Hx, 1), size(Hz, 1), fm, pyx, pyz) end -function PyBeliefPropOSDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=nothing) +function PyBeliefPropOSDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=nothing, osdmethod=nothing, osdorder=0) Hx = parity_checks_x(c) |> collect # TODO should be sparse Hz = parity_checks_z(c) |> collect # TODO should be sparse H = parity_checks(c) @@ -57,8 +57,12 @@ function PyBeliefPropOSDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=n bp_method = get(Dict(:productsum => 0, :minsum => 1, :minsumlog => 2), bpmethod, 0) isnothing(errorrate) || 0≤errorrate≤1 || error(lazy"PyBeliefPropDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].") error_rate = isnothing(errorrate) ? PythonCall.Py(nothing) : errorrate - pyx = ldpc.bposd_decoder(np.array(Hx); max_iter, bp_method, error_rate) # TODO should be sparse - pyz = ldpc.bposd_decoder(np.array(Hz); max_iter, bp_method, error_rate) # TODO should be sparse + isnothing(osdmethod) || osdmethod ∈ (:zeroorder, :exhaustive, :combinationsweep) || error(lazy"PyBeliefPropOSDecoder got an unknown OSD method argument. `osdmethod` must be one of :zeroorder, :exhaustive, :combinationsweep.") + osd_method = get(Dict(:zeroorder => "osd0", :exhaustive => "osde", :combinationsweep => "osdcs"), osdmethod, 0) + 0≤osdorder || error(lazy"PyBeliefPropOSDecoder got an invalid OSD order argument. `osdorder` must be ≥0.") + osd_order = osdorder + pyx = ldpc.bposd_decoder(np.array(Hx); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse + pyz = ldpc.bposd_decoder(np.array(Hz); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse return PyBeliefPropOSDecoder(c, H, Hx, Hz, size(Hx, 1), size(Hz, 1), fm, pyx, pyz) end @@ -67,12 +71,13 @@ 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] + @show (size(row_x), size(row_z)) 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) end -struct PyMatchingDecoder <: AbstractSyndromeDecoder +struct PyMatchingDecoder <: AbstractSyndromeDecoder # TODO all these decoders have the same fields, maybe we can factor out a common type code H Hx diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index dc3db969a..fa56c7510 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -23,7 +23,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, TableDecoder, - BeliefPropDecoder, + BeliefPropDecoder, BitFlipDecoder, PyBeliefPropDecoder, PyBeliefPropOSDecoder, PyMatchingDecoder """Parity check tableau of a code. diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index fb1a75ef2..1c9828b67 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -241,6 +241,16 @@ function BeliefPropDecoder(args...; kwargs...) return ext.BeliefPropDecoder(args...; kwargs...) end +"""An Iterative Bitflip decoder built around tools from `LDPCDecoders.jl`.""" +function BitFlipDecoder(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordLDPCDecodersExt) + if isnothing(ext) + throw("The `BitFlipDecoder` depends on the package `LDPCDecoders` but you have not installed or imported `LDPCDecoders` yet. Immediately after you import `LDPCDecoders`, the `BitFlipDecoder` will be available.") + end + return ext.BitFlipDecoder(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...) ext = Base.get_extension(QuantumClifford, :QuantumCliffordPyQDecodersExt)