From 68230dae33e98411c0b671d078d81b1d024dd612 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Sat, 6 Sep 2025 19:58:11 +0800 Subject: [PATCH 1/8] api in qeccore --- lib/QECCore/src/QECCore.jl | 7 ++ lib/QECCore/src/decoding.jl | 153 ++++++++++++++++++++++++++++++ lib/QECCore/test/test_decoding.jl | 93 ++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 lib/QECCore/src/decoding.jl create mode 100644 lib/QECCore/test/test_decoding.jl diff --git a/lib/QECCore/src/QECCore.jl b/lib/QECCore/src/QECCore.jl index 3e0dc5e3f..e10e18807 100644 --- a/lib/QECCore/src/QECCore.jl +++ b/lib/QECCore/src/QECCore.jl @@ -14,6 +14,12 @@ rate, metacheck_matrix_x, metacheck_matrix_z, metacheck_matrix, bivariate_bicycl generator_polynomial export AbstractECC, AbstractQECC, AbstractCECC, AbstractCSSCode, AbstractDistanceAlg +# ErrorModel +export ErrorModel, depolarization_error_model, isvector, isindependent, random_error_pattern, IndependentVectorSampler + +# Decoding +export AbstractDecoder, decode, syndrome_extraction, DecodingProblem, check_decoding_result + # QEC Codes export Perfect5, Cleve8, Gottesman @@ -29,6 +35,7 @@ export RepCode, ReedMuller, RecursiveReedMuller, Golay, Hamming, GallagerLDPC, G export search_self_orthogonal_rm_codes include("interface.jl") +include("decoding.jl") include("codes/util.jl") # Classical Codes diff --git a/lib/QECCore/src/decoding.jl b/lib/QECCore/src/decoding.jl new file mode 100644 index 000000000..2af17b1e7 --- /dev/null +++ b/lib/QECCore/src/decoding.jl @@ -0,0 +1,153 @@ +""" + $TYPEDEF + +The error model is a dictionary that maps a vector of error bits to a probability array. The probability dictionary is designed to represent any probability distribution on the error bits. + +### Fields + $TYPEDFIELDS +""" +struct ErrorModel{VT<:AbstractArray{Float64}} + """The number of bits in the error model.""" + num_bits::Int + """The probabilities of the error model.""" + probabilities::Dict{Vector{Int},VT} + function ErrorModel(num_bits::Int, probabilities::Dict{Vector{Int},VT}) where VT<:AbstractArray{Float64} + for (error_bits, prob_array) in probabilities + @assert maximum(error_bits) <= num_bits "Maximum element in error bits $error_bits is $(maximum(error_bits)), but num_bits is $num_bits" + expected_size = (fill(2,length(error_bits))...,) + actual_size = size(prob_array) + @assert size(prob_array) == expected_size "The dimension of the probability array must match the length of the error bits vector, and each dimension must be size 2. Expected: $expected_size, Got: $actual_size" + @assert sum(prob_array) == 1 "The sum of the probabilities must be 1, but got $(sum(prob_array))" + end + missing_variables = setdiff(1:num_bits, unique(vcat(keys(probabilities)...))) + @assert isempty(missing_variables) "The following variables are not used in the error model: $missing_variables" + new{VT}(num_bits, probabilities) + end +end + +""" + $TYPEDSIGNATURES + +Create an error model from a vector of error probabilities. The `i`-th element of the vector is the error probability of bit `i`. + +See also: [`depolarization_error_model`](@ref) +""" +function ErrorModel(probabilities::Vector{Float64}) + num_bits = length(probabilities) + probabilities = Dict([i] => [1.0-probabilities[i],probabilities[i]] for i in 1:num_bits) + return ErrorModel(num_bits, probabilities) +end + +""" + $TYPEDSIGNATURES + +Create a depolarization error model from a vector of error probabilities. The `i`-th element of the vector is the depolarization probability of qubit `i`. + +See also: [`depolarization_error_model`](@ref) +""" +function depolarization_error_model(pvec::Vector{Float64}, qubit_num::Int) + @assert length(pvec) == qubit_num "The length of the vector of error probabilities must match the number of qubits" + probabilities = Dict([i,i+qubit_num] => [1.0-pvec[i] pvec[i]/3; pvec[i]/3 pvec[i]/3] for i in 1:qubit_num) + return ErrorModel(2*qubit_num, probabilities) +end + +""" + $TYPEDSIGNATURES + +Create a depolarization error model from a depolarization probability. All qubits have the same depolarization probability. + +See also: [`depolarization_error_model`](@ref) +""" +depolarization_error_model(p::Float64, qubit_num::Int) = depolarization_error_model(fill(p, qubit_num), qubit_num) + +""" + $TYPEDSIGNATURES + +Check if the error model is a vector error model. +""" +isvector(em::ErrorModel) = all(length(key)==1 for key in keys(em.probabilities)) + +""" + $TYPEDSIGNATURES + +Check if the error model is an independent error model. Independent error model means that +""" +function isindependent(em::ErrorModel) + keys_list = collect(keys(em.probabilities)) + for i in 1:length(keys_list)-1 + for j in i+1:length(keys_list) + if !isempty(intersect(keys_list[i], keys_list[j])) + return false + end + end + end + return true +end + +abstract type AbstractSampler end +"""`IndependentVectorSampler` is a simple sampler that only works on vector error model.""" +struct IndependentVectorSampler <: AbstractSampler end + +""" + $TYPEDSIGNATURES + +Generate a random error pattern from the error model. `sampler` is the sampler to use. + +See also: [`IndependentVectorSampler`](@ref) +""" +function random_error_pattern(em::ErrorModel, num_samples::Int, sampler::IndependentVectorSampler) + @assert isvector(em) "The error model must be a vector error model" + return [rand() < em.probabilities[[i]][2] for i in 1:em.num_bits, _ in 1:num_samples] +end + +""" + $TYPEDEF + +A decoding problem is defined by the error model, the check matrix, and the logical matrix. + +### Fields + $TYPEDFIELDS +""" +struct DecodingProblem + error_model::ErrorModel + check_matrix::Matrix{Bool} + logical_matrix::Matrix{Bool} + function DecodingProblem(error_model::ErrorModel, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) + @assert size(check_matrix, 2) == error_model.num_bits "The number of columns of the check matrix must match the number of bits in the error model" + @assert size(logical_matrix, 2) == error_model.num_bits "The number of columns of the logical matrix must match the number of bits in the error model" + new(error_model, check_matrix, logical_matrix) + end +end +# TODO: generate code capacity noise decoding problem. Need to compute the logical operators first. + +""" + $TYPEDSIGNATURES + +Extract the syndrome from the error patterns. +""" +function syndrome_extraction(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) + return Bool.(mod.(check_matrix * error_patterns, 2)) +end +syndrome_extraction(problem::DecodingProblem, error_patterns::Matrix{Bool}) = syndrome_extraction(problem.check_matrix, error_patterns) + + +abstract type AbstractDecoder end + +""" + decode(problem::DecodingProblem,syndrome::Matrix{Bool},decoder::AbstractDecoder) + +Decode the error pattern from the syndrome. +""" +function decode end + +""" + $TYPEDSIGNATURES + +Check if the error pattern is a logical error. +""" +function check_decoding_result(error_pattern1::AbstractMatrix, error_pattern2::AbstractMatrix, problem::DecodingProblem) + ep = error_pattern1 - error_pattern2 + syndrome_test = any(syndrome_extraction(problem.check_matrix, ep), dims=1) + logical_test = any(syndrome_extraction(problem.logical_matrix, ep), dims=1) + return syndrome_test .|| logical_test +end \ No newline at end of file diff --git a/lib/QECCore/test/test_decoding.jl b/lib/QECCore/test/test_decoding.jl new file mode 100644 index 000000000..3b41ae196 --- /dev/null +++ b/lib/QECCore/test/test_decoding.jl @@ -0,0 +1,93 @@ +@testitem "Decoding interfaces" begin + using QECCore + using Test + + @testset "ErrorModel" begin + em1 = ErrorModel([0.4,0.5]) + @test em1.probabilities == Dict([1] => [0.6,0.4], [2] => [0.5,0.5]) + @test em1.num_bits == 2 + @test isvector(em1) + @test isindependent(em1) + + em2 = ErrorModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [3] => [0.0, 1.0])) + @test !isvector(em2) + @test isindependent(em2) + + em3 = ErrorModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [2,3] => [1.0 0.0;0.0 0.0])) + @test !isvector(em3) + @test !isindependent(em3) + + ep4 = depolarization_error_model(0.1, 3) + @test ep4.probabilities == Dict([1,4] => [0.9 0.1/3;0.1/3 0.1/3], [2,5] => [0.9 0.1/3;0.1/3 0.1/3], [3,6] => [0.9 0.1/3;0.1/3 0.1/3]) + @test ep4.num_bits == 6 + @test !isvector(ep4) + @test isindependent(ep4) + + @test_throws AssertionError ErrorModel(3, Dict([1,2,3] => [0.6,0.4,0.5])) + @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.4,0.5],[2] => [0.5,0.5])) + @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) + @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.2],[2] => [0.5,0.5])) + @test_throws AssertionError ErrorModel(3, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) + end + + @testset "IndependentVectorSampler" begin + em = ErrorModel(fill(0.1, 100)) + ep = random_error_pattern(em, 10000, IndependentVectorSampler()) + @test size(ep) == (100, 10000) + @test count(ep)/100/10000 ≈ 0.1 atol = 0.01 + end + + @testset "DecodingProblem" begin + c = Steane7() + pm = parity_matrix(c) + em = ErrorModel(fill(0.1, 14)) + logical_matrix = fill(false, 2, 14) + logical_matrix[1,1] = true + logical_matrix[2,1+7] = true + logical_matrix[1,2] = true + logical_matrix[2,2+7] = true + logical_matrix[1,3] = true + logical_matrix[2,3+7] = true + + problem = DecodingProblem(em, pm, logical_matrix) + + @test_throws AssertionError DecodingProblem(em, pm, fill(false, 2, 13)) + @test_throws AssertionError DecodingProblem(em, pm[:,1:13], logical_matrix) + @test_throws AssertionError DecodingProblem(ErrorModel(fill(0.1, 15)), pm, logical_matrix) + + em = ErrorModel(fill(0.3, 14)) + ep = random_error_pattern(em, 10, IndependentVectorSampler()) + + syndrome = syndrome_extraction(problem, ep) + @test size(syndrome) == (6, 10) + @test size(check_decoding_result(ep, ep, problem)) == (1,10) + + ep = fill(false, 14, 1) + ep[7] = true # Z error on qubit 7 + @test syndrome_extraction(problem, ep) == Bool[true; true; true; false; false; false;;] + + ep2 = copy(ep) + @test !(check_decoding_result(ep, ep2, problem)[]) + + ep2[1] = true + @test (check_decoding_result(ep, ep2, problem)[]) + + # applying a logical operator will lead to a logical error + ep[2] = true + ep[3] = true + @test (check_decoding_result(ep, ep2, problem)[]) + ep2 = copy(ep) + ep2[8:10] .= true + @test (check_decoding_result(ep, ep2, problem)[]) + + # applying a stabilizer will not lead to a logical error + ep2 = copy(ep) + ep2[4:6] .= true + ep2[7] = false + @test !(check_decoding_result(ep, ep2, problem)[]) + + ep2 = copy(ep) + ep2[11:14] .= true + @test !(check_decoding_result(ep, ep2, problem)[]) + end +end From 03e1e6a770d18c90553cf4dd626e5201eb89813a Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Mon, 8 Sep 2025 11:27:13 +0800 Subject: [PATCH 2/8] update docstring --- lib/QECCore/src/decoding.jl | 41 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/QECCore/src/decoding.jl b/lib/QECCore/src/decoding.jl index 2af17b1e7..dfe90e491 100644 --- a/lib/QECCore/src/decoding.jl +++ b/lib/QECCore/src/decoding.jl @@ -1,7 +1,7 @@ """ $TYPEDEF -The error model is a dictionary that maps a vector of error bits to a probability array. The probability dictionary is designed to represent any probability distribution on the error bits. +Represents a probabilistic error model for a system of bits using a factored distribution approach. The model decomposes the joint probability distribution over all bits into smaller, manageable distributions over subsets of bits. ### Fields $TYPEDFIELDS @@ -9,7 +9,20 @@ The error model is a dictionary that maps a vector of error bits to a probabilit struct ErrorModel{VT<:AbstractArray{Float64}} """The number of bits in the error model.""" num_bits::Int - """The probabilities of the error model.""" + """Dictionary defining the factored probability distributions. + + - **Keys**: `Vector{Int}` specifying which subset of bits the distribution covers + (e.g., `[1,3]` means this distribution covers bits 1 and 3) + - **Values**: `VT` multi-dimensional probability array where: + - Dimensionality = length of the key vector + - Each dimension has size 2 (representing classical bit states 0/1 or error/no-error) + - Values are non-negative probabilities summing to 1 + + Example: `[1,2] => [0.4 0.3; 0.2 0.1]` represents: + - P(bit1=0, bit2=0) = 0.4 + - P(bit1=0, bit2=1) = 0.3 + - P(bit1=1, bit2=0) = 0.2 + - P(bit1=1, bit2=1) = 0.1""" probabilities::Dict{Vector{Int},VT} function ErrorModel(num_bits::Int, probabilities::Dict{Vector{Int},VT}) where VT<:AbstractArray{Float64} for (error_bits, prob_array) in probabilities @@ -42,8 +55,6 @@ end $TYPEDSIGNATURES Create a depolarization error model from a vector of error probabilities. The `i`-th element of the vector is the depolarization probability of qubit `i`. - -See also: [`depolarization_error_model`](@ref) """ function depolarization_error_model(pvec::Vector{Float64}, qubit_num::Int) @assert length(pvec) == qubit_num "The length of the vector of error probabilities must match the number of qubits" @@ -55,8 +66,6 @@ end $TYPEDSIGNATURES Create a depolarization error model from a depolarization probability. All qubits have the same depolarization probability. - -See also: [`depolarization_error_model`](@ref) """ depolarization_error_model(p::Float64, qubit_num::Int) = depolarization_error_model(fill(p, qubit_num), qubit_num) @@ -103,19 +112,29 @@ end """ $TYPEDEF -A decoding problem is defined by the error model, the check matrix, and the logical matrix. +Represents a quantum error correction decoding problem by combining an error model with the code's stabilizer checks and logical operators. This structure forms the complete specification needed to perform quantum error correction decoding. + +See also: [`ErrorModel`](@ref) ### Fields $TYPEDFIELDS """ -struct DecodingProblem - error_model::ErrorModel +struct DecodingProblem{VT} + """Probabilistic error model for the bits""" + error_model::ErrorModel{VT} + """Parity check matrix + - **Matrix dimensions**: [num_checks × num_bits] + - **Matrix elements**: Boolean (false=0, true=1) + - **Operation**: Syndrome = check_matrix × error_vector (mod 2)""" check_matrix::Matrix{Bool} + """Logical operators + - **Matrix dimensions**: [num_logicals × num_bits] + - **Matrix elements**: Boolean (false=0, true=1)""" logical_matrix::Matrix{Bool} - function DecodingProblem(error_model::ErrorModel, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) + function DecodingProblem(error_model::ErrorModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) @assert size(check_matrix, 2) == error_model.num_bits "The number of columns of the check matrix must match the number of bits in the error model" @assert size(logical_matrix, 2) == error_model.num_bits "The number of columns of the logical matrix must match the number of bits in the error model" - new(error_model, check_matrix, logical_matrix) + new{VT}(error_model, check_matrix, logical_matrix) end end # TODO: generate code capacity noise decoding problem. Need to compute the logical operators first. From 1bc3081c7a5c44734e248c777979cbabe9d18f78 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Mon, 8 Sep 2025 12:17:47 +0800 Subject: [PATCH 3/8] format file --- lib/QECCore/src/decoding.jl | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/QECCore/src/decoding.jl b/lib/QECCore/src/decoding.jl index dfe90e491..6fa9812e2 100644 --- a/lib/QECCore/src/decoding.jl +++ b/lib/QECCore/src/decoding.jl @@ -1,7 +1,9 @@ """ $TYPEDEF -Represents a probabilistic error model for a system of bits using a factored distribution approach. The model decomposes the joint probability distribution over all bits into smaller, manageable distributions over subsets of bits. +Represents a probabilistic error model for a system of bits using a factored +distribution approach. The model decomposes the joint probability distribution +over all bits into smaller, manageable distributions over subsets of bits. ### Fields $TYPEDFIELDS @@ -10,14 +12,14 @@ struct ErrorModel{VT<:AbstractArray{Float64}} """The number of bits in the error model.""" num_bits::Int """Dictionary defining the factored probability distributions. - + - **Keys**: `Vector{Int}` specifying which subset of bits the distribution covers (e.g., `[1,3]` means this distribution covers bits 1 and 3) - **Values**: `VT` multi-dimensional probability array where: - Dimensionality = length of the key vector - Each dimension has size 2 (representing classical bit states 0/1 or error/no-error) - Values are non-negative probabilities summing to 1 - + Example: `[1,2] => [0.4 0.3; 0.2 0.1]` represents: - P(bit1=0, bit2=0) = 0.4 - P(bit1=0, bit2=1) = 0.3 @@ -27,7 +29,7 @@ struct ErrorModel{VT<:AbstractArray{Float64}} function ErrorModel(num_bits::Int, probabilities::Dict{Vector{Int},VT}) where VT<:AbstractArray{Float64} for (error_bits, prob_array) in probabilities @assert maximum(error_bits) <= num_bits "Maximum element in error bits $error_bits is $(maximum(error_bits)), but num_bits is $num_bits" - expected_size = (fill(2,length(error_bits))...,) + expected_size = (fill(2, length(error_bits))...,) actual_size = size(prob_array) @assert size(prob_array) == expected_size "The dimension of the probability array must match the length of the error bits vector, and each dimension must be size 2. Expected: $expected_size, Got: $actual_size" @assert sum(prob_array) == 1 "The sum of the probabilities must be 1, but got $(sum(prob_array))" @@ -47,19 +49,20 @@ See also: [`depolarization_error_model`](@ref) """ function ErrorModel(probabilities::Vector{Float64}) num_bits = length(probabilities) - probabilities = Dict([i] => [1.0-probabilities[i],probabilities[i]] for i in 1:num_bits) + probabilities = Dict([i] => [1.0 - probabilities[i], probabilities[i]] for i in 1:num_bits) return ErrorModel(num_bits, probabilities) end """ $TYPEDSIGNATURES -Create a depolarization error model from a vector of error probabilities. The `i`-th element of the vector is the depolarization probability of qubit `i`. +Create a depolarization error model from a vector of error probabilities. +The `i`-th element of the vector is the depolarization probability of qubit `i`. """ function depolarization_error_model(pvec::Vector{Float64}, qubit_num::Int) @assert length(pvec) == qubit_num "The length of the vector of error probabilities must match the number of qubits" - probabilities = Dict([i,i+qubit_num] => [1.0-pvec[i] pvec[i]/3; pvec[i]/3 pvec[i]/3] for i in 1:qubit_num) - return ErrorModel(2*qubit_num, probabilities) + probabilities = Dict([i, i + qubit_num] => [1.0-pvec[i] pvec[i]/3; pvec[i]/3 pvec[i]/3] for i in 1:qubit_num) + return ErrorModel(2 * qubit_num, probabilities) end """ @@ -74,7 +77,7 @@ depolarization_error_model(p::Float64, qubit_num::Int) = depolarization_error_mo Check if the error model is a vector error model. """ -isvector(em::ErrorModel) = all(length(key)==1 for key in keys(em.probabilities)) +isvector(em::ErrorModel) = all(length(key) == 1 for key in keys(em.probabilities)) """ $TYPEDSIGNATURES @@ -104,7 +107,7 @@ Generate a random error pattern from the error model. `sampler` is the sampler t See also: [`IndependentVectorSampler`](@ref) """ -function random_error_pattern(em::ErrorModel, num_samples::Int, sampler::IndependentVectorSampler) +function random_error_pattern(em::ErrorModel, num_samples::Int, sampler::IndependentVectorSampler) @assert isvector(em) "The error model must be a vector error model" return [rand() < em.probabilities[[i]][2] for i in 1:em.num_bits, _ in 1:num_samples] end @@ -112,7 +115,9 @@ end """ $TYPEDEF -Represents a quantum error correction decoding problem by combining an error model with the code's stabilizer checks and logical operators. This structure forms the complete specification needed to perform quantum error correction decoding. +Represents a quantum error correction decoding problem by combining an error +model with the code's stabilizer checks and logical operators. This structure +forms the complete specification needed to perform quantum error correction decoding. See also: [`ErrorModel`](@ref) @@ -144,7 +149,7 @@ end Extract the syndrome from the error patterns. """ -function syndrome_extraction(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) +function syndrome_extraction(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) return Bool.(mod.(check_matrix * error_patterns, 2)) end syndrome_extraction(problem::DecodingProblem, error_patterns::Matrix{Bool}) = syndrome_extraction(problem.check_matrix, error_patterns) @@ -169,4 +174,4 @@ function check_decoding_result(error_pattern1::AbstractMatrix, error_pattern2::A syndrome_test = any(syndrome_extraction(problem.check_matrix, ep), dims=1) logical_test = any(syndrome_extraction(problem.logical_matrix, ep), dims=1) return syndrome_test .|| logical_test -end \ No newline at end of file +end From c1662c1f4fb555152f0754ae71e2a15a37526896 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Mon, 8 Sep 2025 12:30:42 +0800 Subject: [PATCH 4/8] Fix bug: add missing VT --- lib/QECCore/src/decoding.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/QECCore/src/decoding.jl b/lib/QECCore/src/decoding.jl index 6fa9812e2..0ffea0566 100644 --- a/lib/QECCore/src/decoding.jl +++ b/lib/QECCore/src/decoding.jl @@ -136,7 +136,7 @@ struct DecodingProblem{VT} - **Matrix dimensions**: [num_logicals × num_bits] - **Matrix elements**: Boolean (false=0, true=1)""" logical_matrix::Matrix{Bool} - function DecodingProblem(error_model::ErrorModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) + function DecodingProblem(error_model::ErrorModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) where VT @assert size(check_matrix, 2) == error_model.num_bits "The number of columns of the check matrix must match the number of bits in the error model" @assert size(logical_matrix, 2) == error_model.num_bits "The number of columns of the logical matrix must match the number of bits in the error model" new{VT}(error_model, check_matrix, logical_matrix) From eb0a1f6e38e87fac4b93b9eb7456b4cd5d9a713d Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Wed, 10 Sep 2025 17:37:10 +0800 Subject: [PATCH 5/8] udpate apis --- lib/QECCore/src/QECCore.jl | 4 +- .../src/{decoding.jl => decoding_impl.jl} | 58 +++++-------- lib/QECCore/src/decoding_interface.jl | 81 +++++++++++++++++++ 3 files changed, 106 insertions(+), 37 deletions(-) rename lib/QECCore/src/{decoding.jl => decoding_impl.jl} (73%) create mode 100644 lib/QECCore/src/decoding_interface.jl diff --git a/lib/QECCore/src/QECCore.jl b/lib/QECCore/src/QECCore.jl index e10e18807..919401142 100644 --- a/lib/QECCore/src/QECCore.jl +++ b/lib/QECCore/src/QECCore.jl @@ -35,7 +35,9 @@ export RepCode, ReedMuller, RecursiveReedMuller, Golay, Hamming, GallagerLDPC, G export search_self_orthogonal_rm_codes include("interface.jl") -include("decoding.jl") +include("decoding_interface.jl") +include("decoding_impl.jl") + include("codes/util.jl") # Classical Codes diff --git a/lib/QECCore/src/decoding.jl b/lib/QECCore/src/decoding_impl.jl similarity index 73% rename from lib/QECCore/src/decoding.jl rename to lib/QECCore/src/decoding_impl.jl index 0ffea0566..ba7c88562 100644 --- a/lib/QECCore/src/decoding.jl +++ b/lib/QECCore/src/decoding_impl.jl @@ -8,8 +8,8 @@ over all bits into smaller, manageable distributions over subsets of bits. ### Fields $TYPEDFIELDS """ -struct ErrorModel{VT<:AbstractArray{Float64}} - """The number of bits in the error model.""" +struct FactoredBitNoiseModel{VT<:AbstractArray{Float64}} <: AbstractNoiseModel + """The number of bits in the noise model.""" num_bits::Int """Dictionary defining the factored probability distributions. @@ -26,7 +26,7 @@ struct ErrorModel{VT<:AbstractArray{Float64}} - P(bit1=1, bit2=0) = 0.2 - P(bit1=1, bit2=1) = 0.1""" probabilities::Dict{Vector{Int},VT} - function ErrorModel(num_bits::Int, probabilities::Dict{Vector{Int},VT}) where VT<:AbstractArray{Float64} + function FactoredBitNoiseModel(num_bits::Int, probabilities::Dict{Vector{Int},VT}) where VT<:AbstractArray{Float64} for (error_bits, prob_array) in probabilities @assert maximum(error_bits) <= num_bits "Maximum element in error bits $error_bits is $(maximum(error_bits)), but num_bits is $num_bits" expected_size = (fill(2, length(error_bits))...,) @@ -47,10 +47,10 @@ Create an error model from a vector of error probabilities. The `i`-th element o See also: [`depolarization_error_model`](@ref) """ -function ErrorModel(probabilities::Vector{Float64}) +function FactoredBitNoiseModel(probabilities::Vector{Float64}) num_bits = length(probabilities) probabilities = Dict([i] => [1.0 - probabilities[i], probabilities[i]] for i in 1:num_bits) - return ErrorModel(num_bits, probabilities) + return FactoredBitNoiseModel(num_bits, probabilities) end """ @@ -62,7 +62,7 @@ The `i`-th element of the vector is the depolarization probability of qubit `i`. function depolarization_error_model(pvec::Vector{Float64}, qubit_num::Int) @assert length(pvec) == qubit_num "The length of the vector of error probabilities must match the number of qubits" probabilities = Dict([i, i + qubit_num] => [1.0-pvec[i] pvec[i]/3; pvec[i]/3 pvec[i]/3] for i in 1:qubit_num) - return ErrorModel(2 * qubit_num, probabilities) + return FactoredBitNoiseModel(2 * qubit_num, probabilities) end """ @@ -77,14 +77,14 @@ depolarization_error_model(p::Float64, qubit_num::Int) = depolarization_error_mo Check if the error model is a vector error model. """ -isvector(em::ErrorModel) = all(length(key) == 1 for key in keys(em.probabilities)) +isvector(em::FactoredBitNoiseModel) = all(length(key) == 1 for key in keys(em.probabilities)) """ $TYPEDSIGNATURES Check if the error model is an independent error model. Independent error model means that """ -function isindependent(em::ErrorModel) +function isindependent(em::FactoredBitNoiseModel) keys_list = collect(keys(em.probabilities)) for i in 1:length(keys_list)-1 for j in i+1:length(keys_list) @@ -96,7 +96,6 @@ function isindependent(em::ErrorModel) return true end -abstract type AbstractSampler end """`IndependentVectorSampler` is a simple sampler that only works on vector error model.""" struct IndependentVectorSampler <: AbstractSampler end @@ -107,11 +106,12 @@ Generate a random error pattern from the error model. `sampler` is the sampler t See also: [`IndependentVectorSampler`](@ref) """ -function random_error_pattern(em::ErrorModel, num_samples::Int, sampler::IndependentVectorSampler) +function sample(em::FactoredBitNoiseModel, num_samples::Int, sampler::IndependentVectorSampler) @assert isvector(em) "The error model must be a vector error model" return [rand() < em.probabilities[[i]][2] for i in 1:em.num_bits, _ in 1:num_samples] end +abstract type AbstractDecodingProblem end """ $TYPEDEF @@ -119,59 +119,45 @@ Represents a quantum error correction decoding problem by combining an error model with the code's stabilizer checks and logical operators. This structure forms the complete specification needed to perform quantum error correction decoding. -See also: [`ErrorModel`](@ref) +See also: [`FactoredBitErrorModel`](@ref) ### Fields $TYPEDFIELDS """ -struct DecodingProblem{VT} +struct DetectorModelProblem{VT} <: AbstractDecodingProblem """Probabilistic error model for the bits""" - error_model::ErrorModel{VT} + error_model::FactoredBitNoiseModel{VT} """Parity check matrix - - **Matrix dimensions**: [num_checks × num_bits] + - **Matrix dimensions**: [`num_checks` × `num_bits`] - **Matrix elements**: Boolean (false=0, true=1) - - **Operation**: Syndrome = check_matrix × error_vector (mod 2)""" + - **Operation**: Syndrome = `check_matrix` × `error_vector` (mod 2)""" check_matrix::Matrix{Bool} """Logical operators - - **Matrix dimensions**: [num_logicals × num_bits] + - **Matrix dimensions**: [`num_logicals` × `num_bits`] - **Matrix elements**: Boolean (false=0, true=1)""" logical_matrix::Matrix{Bool} - function DecodingProblem(error_model::ErrorModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) where VT + function DetectorModelProblem(error_model::FactoredBitNoiseModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) where VT @assert size(check_matrix, 2) == error_model.num_bits "The number of columns of the check matrix must match the number of bits in the error model" @assert size(logical_matrix, 2) == error_model.num_bits "The number of columns of the logical matrix must match the number of bits in the error model" new{VT}(error_model, check_matrix, logical_matrix) end end -# TODO: generate code capacity noise decoding problem. Need to compute the logical operators first. -""" - $TYPEDSIGNATURES -Extract the syndrome from the error patterns. -""" -function syndrome_extraction(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) +function syndrome(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) return Bool.(mod.(check_matrix * error_patterns, 2)) end -syndrome_extraction(problem::DecodingProblem, error_patterns::Matrix{Bool}) = syndrome_extraction(problem.check_matrix, error_patterns) - +syndrome(problem::DetectorModelProblem, error_patterns::Matrix{Bool}) = syndrome(problem.check_matrix, error_patterns) -abstract type AbstractDecoder end - -""" - decode(problem::DecodingProblem,syndrome::Matrix{Bool},decoder::AbstractDecoder) - -Decode the error pattern from the syndrome. -""" -function decode end """ $TYPEDSIGNATURES Check if the error pattern is a logical error. """ -function check_decoding_result(error_pattern1::AbstractMatrix, error_pattern2::AbstractMatrix, problem::DecodingProblem) +function decoding_error_rate(error_pattern1::AbstractMatrix, error_pattern2::AbstractMatrix, problem::DetectorModelProblem) ep = error_pattern1 - error_pattern2 - syndrome_test = any(syndrome_extraction(problem.check_matrix, ep), dims=1) - logical_test = any(syndrome_extraction(problem.logical_matrix, ep), dims=1) + syndrome_test = any(syndrome(problem.check_matrix, ep), dims=1) + logical_test = any(syndrome(problem.logical_matrix, ep), dims=1) return syndrome_test .|| logical_test end diff --git a/lib/QECCore/src/decoding_interface.jl b/lib/QECCore/src/decoding_interface.jl new file mode 100644 index 000000000..39e18e40e --- /dev/null +++ b/lib/QECCore/src/decoding_interface.jl @@ -0,0 +1,81 @@ +abstract type AbstractDecodingProblem end +abstract type AbstractNoiseModel end +abstract type AbstractDecodingScenario end + +""" + decoding_problem(code::AbstractCode, noise_model::AbstractNoiseModel, decoding_scenario::AbstractDecodingScenario) + +Generate a decoding problem from a code, a noise model, and a decoding scenario. + +### Inputs +- `code::AbstractCode`: The code to decode. +- `noise_model::AbstractNoiseModel`: The noise model to use. +- `decoding_scenario::AbstractDecodingScenario`: The decoding scenario to use. + +### Outputs +- `problem::AbstractDecodingProblem`: The decoding problem. +""" +function decoding_problem end + +abstract type AbstractDecodingSamples end +abstract type AbstractSampler end + +""" + sample(problem::AbstractDecodingProblem, sampler::AbstractSampler) + +Sample from the decoding problem. + +### Inputs +- `problem::AbstractDecodingProblem`: The decoding problem to sample from. +- `sampler::AbstractSampler`: The sampler to use. + +### Outputs +- `samples::AbstractDecodingSamples`: The sampled syndrome. +""" +function sample end + +abstract type AbstractSyndrome end +""" + syndrome(samples::AbstractDecodingSamples) + +Extract the syndrome from the samples. + +### Inputs +- `samples::AbstractDecodingSamples`: The sampled data. + +### Outputs +- `syndrome::AbstractSyndrome`: The extracted syndrome. +""" +function syndrome end + +abstract type AbstractDecoder end + +""" + decode(problem::AbstractDecodingProblem, syndrome::AbstractSyndrome, decoder::AbstractDecoder) + +Decode the syndrome using the decoder. + +### Inputs +- `problem::AbstractDecodingProblem`: The decoding problem to decode. +- `syndrome::AbstractSyndrome`: The syndrome to decode. +- `decoder::AbstractDecoder`: The decoder to use. + +### Outputs +- `decoding_result::AbstractMatrix`: The decoded result. +""" +function decode end + +""" + decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractMatrix) + +Calculate the error rate of the decoding result. + +### Inputs +- `problem::AbstractDecodingProblem`: The decoding problem to validate. +- `syndrome::AbstractSyndrome`: The syndrome to validate. +- `decoding_result::AbstractMatrix`: The decoded result to validate. + +### Outputs +- `rate::Float64`: The error rate of the decoding result. +""" +function decoding_error_rate end From eb6ddb60815efe6bd37c78dc2a856116315bafca Mon Sep 17 00:00:00 2001 From: nzy Date: Thu, 11 Sep 2025 15:14:10 +0800 Subject: [PATCH 6/8] update docs and test --- lib/QECCore/docs/src/decoding_pipline.md | 52 +++++++++++++++++++++ lib/QECCore/src/QECCore.jl | 6 +-- lib/QECCore/src/decoding_impl.jl | 56 +++++++++++++++-------- lib/QECCore/test/test_decoding.jl | 58 ++++++++++++------------ 4 files changed, 123 insertions(+), 49 deletions(-) create mode 100644 lib/QECCore/docs/src/decoding_pipline.md diff --git a/lib/QECCore/docs/src/decoding_pipline.md b/lib/QECCore/docs/src/decoding_pipline.md new file mode 100644 index 000000000..7711eb57d --- /dev/null +++ b/lib/QECCore/docs/src/decoding_pipline.md @@ -0,0 +1,52 @@ +# Quantum Error Correction Decoding Interface + +## Overview + +`decoding_interface.jl` defines the core abstract interfaces for quantum error correction decoding systems, providing a modular framework for handling decoding problems of quantum codes. + +## Core Abstract Types + +### Problem Definition +- `AbstractDecodingProblem`: Abstract base class for decoding problems +- `AbstractNoiseModel`: Abstract base class for noise models +- `AbstractDecodingScenario`: Abstract base class for decoding scenarios + +### Sampling and Data +- `AbstractDecodingSamples`: Abstract base class for decoding samples +- `AbstractSampler`: Abstract base class for samplers +- `AbstractSyndrome`: Abstract base class for syndromes + +### Decoder +- `AbstractDecoder`: Abstract base class for decoders + +## Main Interface Functions + +### 1. Problem Generation +```julia +decoding_problem(code::AbstractCode, noise_model::AbstractNoiseModel, decoding_scenario::AbstractDecodingScenario) -> AbstractDecodingProblem +``` +Generate a decoding problem from a quantum code, noise model, and decoding scenario. + +### 2. Sampling +```julia +sample(problem::AbstractDecodingProblem, sampler::AbstractSampler) -> AbstractDecodingSamples +``` +Sample from the decoding problem to generate decoding samples. + +### 3. Syndrome Extraction +```julia +syndrome(samples::AbstractDecodingSamples) -> AbstractSyndrome +``` +Extract syndrome from sampled data. + +### 4. Decoding +```julia +decode(problem::AbstractDecodingProblem, syndrome::AbstractSyndrome, decoder::AbstractDecoder) -> AbstractMatrix +``` +Decode the syndrome using the decoder and return the decoding result. + +### 5. Error Rate Calculation +```julia +decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractMatrix) -> Float64 +``` +Calculate the error rate of the decoding result for performance evaluation. diff --git a/lib/QECCore/src/QECCore.jl b/lib/QECCore/src/QECCore.jl index 919401142..56e31d9c9 100644 --- a/lib/QECCore/src/QECCore.jl +++ b/lib/QECCore/src/QECCore.jl @@ -14,11 +14,11 @@ rate, metacheck_matrix_x, metacheck_matrix_z, metacheck_matrix, bivariate_bicycl generator_polynomial export AbstractECC, AbstractQECC, AbstractCECC, AbstractCSSCode, AbstractDistanceAlg -# ErrorModel -export ErrorModel, depolarization_error_model, isvector, isindependent, random_error_pattern, IndependentVectorSampler +# FactoredBitNoiseModel +export FactoredBitNoiseModel, depolarization_error_model, isvector, isindependent, IndependentVectorSampler, BitStringSamples # Decoding -export AbstractDecoder, decode, syndrome_extraction, DecodingProblem, check_decoding_result +export AbstractDecoder, decode, sample, decoding_error_rate, DetectorModelProblem, check_decoding_result # QEC Codes export Perfect5, Cleve8, Gottesman diff --git a/lib/QECCore/src/decoding_impl.jl b/lib/QECCore/src/decoding_impl.jl index ba7c88562..004bad31f 100644 --- a/lib/QECCore/src/decoding_impl.jl +++ b/lib/QECCore/src/decoding_impl.jl @@ -99,19 +99,11 @@ end """`IndependentVectorSampler` is a simple sampler that only works on vector error model.""" struct IndependentVectorSampler <: AbstractSampler end -""" - $TYPEDSIGNATURES - -Generate a random error pattern from the error model. `sampler` is the sampler to use. - -See also: [`IndependentVectorSampler`](@ref) -""" function sample(em::FactoredBitNoiseModel, num_samples::Int, sampler::IndependentVectorSampler) @assert isvector(em) "The error model must be a vector error model" return [rand() < em.probabilities[[i]][2] for i in 1:em.num_bits, _ in 1:num_samples] end -abstract type AbstractDecodingProblem end """ $TYPEDEF @@ -124,9 +116,9 @@ See also: [`FactoredBitErrorModel`](@ref) ### Fields $TYPEDFIELDS """ -struct DetectorModelProblem{VT} <: AbstractDecodingProblem +struct DetectorModelProblem{NMT<:AbstractNoiseModel} <: AbstractDecodingProblem """Probabilistic error model for the bits""" - error_model::FactoredBitNoiseModel{VT} + error_model::NMT """Parity check matrix - **Matrix dimensions**: [`num_checks` × `num_bits`] - **Matrix elements**: Boolean (false=0, true=1) @@ -136,28 +128,56 @@ struct DetectorModelProblem{VT} <: AbstractDecodingProblem - **Matrix dimensions**: [`num_logicals` × `num_bits`] - **Matrix elements**: Boolean (false=0, true=1)""" logical_matrix::Matrix{Bool} - function DetectorModelProblem(error_model::FactoredBitNoiseModel{VT}, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) where VT + function DetectorModelProblem(error_model::NMT, check_matrix::Matrix{Bool}, logical_matrix::Matrix{Bool}) where NMT<:AbstractNoiseModel @assert size(check_matrix, 2) == error_model.num_bits "The number of columns of the check matrix must match the number of bits in the error model" @assert size(logical_matrix, 2) == error_model.num_bits "The number of columns of the logical matrix must match the number of bits in the error model" - new{VT}(error_model, check_matrix, logical_matrix) + new{NMT}(error_model, check_matrix, logical_matrix) end end +""" + $TYPEDEF + +Represents a set of bit string samples. + +### Fields + $TYPEDFIELDS +""" +struct BitStringSamples <: AbstractDecodingSamples + """Physical bits""" + physical_bits::Matrix{Bool} + """Check bits""" + check_bits::Matrix{Bool} + """Logical bits""" + logical_bits::Matrix{Bool} +end + +function sample(problem::DetectorModelProblem{NMT}, num_samples::Int, sampler::IndependentVectorSampler) where NMT<:FactoredBitNoiseModel + physical_bits = sample(problem.error_model, num_samples, sampler) + check_bits = measure_syndrome(problem.check_matrix, physical_bits) + logical_bits = measure_syndrome(problem.logical_matrix, physical_bits) + return BitStringSamples(physical_bits, check_bits, logical_bits) +end -function syndrome(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) +function measure_syndrome(check_matrix::AbstractMatrix, error_patterns::AbstractMatrix) return Bool.(mod.(check_matrix * error_patterns, 2)) end -syndrome(problem::DetectorModelProblem, error_patterns::Matrix{Bool}) = syndrome(problem.check_matrix, error_patterns) +measure_syndrome(problem::DetectorModelProblem, error_patterns::Matrix{Bool}) = measure_syndrome(problem.check_matrix, error_patterns) +syndrome(samples::BitStringSamples) = samples.check_bits """ $TYPEDSIGNATURES Check if the error pattern is a logical error. """ -function decoding_error_rate(error_pattern1::AbstractMatrix, error_pattern2::AbstractMatrix, problem::DetectorModelProblem) - ep = error_pattern1 - error_pattern2 - syndrome_test = any(syndrome(problem.check_matrix, ep), dims=1) - logical_test = any(syndrome(problem.logical_matrix, ep), dims=1) +function decoding_error_rate(problem::DetectorModelProblem, samples::BitStringSamples, decoding_result::AbstractMatrix) + ep = decoding_result - samples.physical_bits + return count(check_decoding_result(problem, ep)) / size(decoding_result, 2) +end + +function check_decoding_result(problem::DetectorModelProblem,decoding_result_diff::AbstractMatrix) + syndrome_test = any(measure_syndrome(problem.check_matrix, decoding_result_diff), dims=1) + logical_test = any(measure_syndrome(problem.logical_matrix, decoding_result_diff), dims=1) return syndrome_test .|| logical_test end diff --git a/lib/QECCore/test/test_decoding.jl b/lib/QECCore/test/test_decoding.jl index 3b41ae196..378815829 100644 --- a/lib/QECCore/test/test_decoding.jl +++ b/lib/QECCore/test/test_decoding.jl @@ -2,18 +2,18 @@ using QECCore using Test - @testset "ErrorModel" begin - em1 = ErrorModel([0.4,0.5]) + @testset "FactoredBitNoiseModel" begin + em1 = FactoredBitNoiseModel([0.4,0.5]) @test em1.probabilities == Dict([1] => [0.6,0.4], [2] => [0.5,0.5]) @test em1.num_bits == 2 @test isvector(em1) @test isindependent(em1) - em2 = ErrorModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [3] => [0.0, 1.0])) + em2 = FactoredBitNoiseModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [3] => [0.0, 1.0])) @test !isvector(em2) @test isindependent(em2) - em3 = ErrorModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [2,3] => [1.0 0.0;0.0 0.0])) + em3 = FactoredBitNoiseModel(3, Dict([1,2] => [1.0 0.0;0.0 0.0], [2,3] => [1.0 0.0;0.0 0.0])) @test !isvector(em3) @test !isindependent(em3) @@ -23,24 +23,24 @@ @test !isvector(ep4) @test isindependent(ep4) - @test_throws AssertionError ErrorModel(3, Dict([1,2,3] => [0.6,0.4,0.5])) - @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.4,0.5],[2] => [0.5,0.5])) - @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) - @test_throws AssertionError ErrorModel(2, Dict([1] => [0.6,0.2],[2] => [0.5,0.5])) - @test_throws AssertionError ErrorModel(3, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) + @test_throws AssertionError FactoredBitNoiseModel(3, Dict([1,2,3] => [0.6,0.4,0.5])) + @test_throws AssertionError FactoredBitNoiseModel(2, Dict([1] => [0.6,0.4,0.5],[2] => [0.5,0.5])) + @test_throws AssertionError FactoredBitNoiseModel(2, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) + @test_throws AssertionError FactoredBitNoiseModel(2, Dict([1] => [0.6,0.2],[2] => [0.5,0.5])) + @test_throws AssertionError FactoredBitNoiseModel(3, Dict([1] => [0.6,0.4],[3] => [0.5,0.5])) end @testset "IndependentVectorSampler" begin - em = ErrorModel(fill(0.1, 100)) - ep = random_error_pattern(em, 10000, IndependentVectorSampler()) + em = FactoredBitNoiseModel(fill(0.1, 100)) + ep = sample(em, 10000, IndependentVectorSampler()) @test size(ep) == (100, 10000) @test count(ep)/100/10000 ≈ 0.1 atol = 0.01 end - @testset "DecodingProblem" begin + @testset "DetectorModelProblem" begin c = Steane7() pm = parity_matrix(c) - em = ErrorModel(fill(0.1, 14)) + em = FactoredBitNoiseModel(fill(0.1, 14)) logical_matrix = fill(false, 2, 14) logical_matrix[1,1] = true logical_matrix[2,1+7] = true @@ -49,45 +49,47 @@ logical_matrix[1,3] = true logical_matrix[2,3+7] = true - problem = DecodingProblem(em, pm, logical_matrix) + problem = DetectorModelProblem(em, pm, logical_matrix) - @test_throws AssertionError DecodingProblem(em, pm, fill(false, 2, 13)) - @test_throws AssertionError DecodingProblem(em, pm[:,1:13], logical_matrix) - @test_throws AssertionError DecodingProblem(ErrorModel(fill(0.1, 15)), pm, logical_matrix) + @test_throws AssertionError DetectorModelProblem(em, pm, fill(false, 2, 13)) + @test_throws AssertionError DetectorModelProblem(em, pm[:,1:13], logical_matrix) + @test_throws AssertionError DetectorModelProblem(FactoredBitNoiseModel(fill(0.1, 15)), pm, logical_matrix) - em = ErrorModel(fill(0.3, 14)) - ep = random_error_pattern(em, 10, IndependentVectorSampler()) + em = FactoredBitNoiseModel(fill(0.3, 14)) + ep = sample(em, 10, IndependentVectorSampler()) - syndrome = syndrome_extraction(problem, ep) + syndrome = QECCore.measure_syndrome(problem, ep) @test size(syndrome) == (6, 10) - @test size(check_decoding_result(ep, ep, problem)) == (1,10) ep = fill(false, 14, 1) ep[7] = true # Z error on qubit 7 - @test syndrome_extraction(problem, ep) == Bool[true; true; true; false; false; false;;] + @test QECCore.measure_syndrome(problem, ep) == Bool[true; true; true; false; false; false;;] ep2 = copy(ep) - @test !(check_decoding_result(ep, ep2, problem)[]) + @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) ep2[1] = true - @test (check_decoding_result(ep, ep2, problem)[]) + @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) # applying a logical operator will lead to a logical error ep[2] = true ep[3] = true - @test (check_decoding_result(ep, ep2, problem)[]) + @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) ep2 = copy(ep) ep2[8:10] .= true - @test (check_decoding_result(ep, ep2, problem)[]) + @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) # applying a stabilizer will not lead to a logical error ep2 = copy(ep) ep2[4:6] .= true ep2[7] = false - @test !(check_decoding_result(ep, ep2, problem)[]) + @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) ep2 = copy(ep) ep2[11:14] .= true - @test !(check_decoding_result(ep, ep2, problem)[]) + @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) + + samples = sample(problem, 1000, IndependentVectorSampler()) + @test decoding_error_rate(problem, samples, samples.physical_bits) == 0.0 end end From 3602ce722b0fe0fef5a4c0bf461fdd64dbc85961 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 12 Sep 2025 12:10:57 +0800 Subject: [PATCH 7/8] update decoding res --- ...{decoding_pipline.md => decoding_pipeline.md} | 5 +++-- lib/QECCore/src/QECCore.jl | 8 ++++---- lib/QECCore/src/decoding_impl.jl | 16 +++++++++++----- lib/QECCore/src/decoding_interface.jl | 8 ++++---- lib/QECCore/test/test_decoding.jl | 14 +++++++------- 5 files changed, 29 insertions(+), 22 deletions(-) rename lib/QECCore/docs/src/{decoding_pipline.md => decoding_pipeline.md} (89%) diff --git a/lib/QECCore/docs/src/decoding_pipline.md b/lib/QECCore/docs/src/decoding_pipeline.md similarity index 89% rename from lib/QECCore/docs/src/decoding_pipline.md rename to lib/QECCore/docs/src/decoding_pipeline.md index 7711eb57d..7a376a055 100644 --- a/lib/QECCore/docs/src/decoding_pipline.md +++ b/lib/QECCore/docs/src/decoding_pipeline.md @@ -18,6 +18,7 @@ ### Decoder - `AbstractDecoder`: Abstract base class for decoders +- `AbstractDecodingResult`: Abstract base class for decoding results ## Main Interface Functions @@ -41,12 +42,12 @@ Extract syndrome from sampled data. ### 4. Decoding ```julia -decode(problem::AbstractDecodingProblem, syndrome::AbstractSyndrome, decoder::AbstractDecoder) -> AbstractMatrix +decode(problem::AbstractDecodingProblem, syndrome::AbstractSyndrome, decoder::AbstractDecoder) -> AbstractDecodingResult ``` Decode the syndrome using the decoder and return the decoding result. ### 5. Error Rate Calculation ```julia -decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractMatrix) -> Float64 +decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractDecodingResult) -> Float64 ``` Calculate the error rate of the decoding result for performance evaluation. diff --git a/lib/QECCore/src/QECCore.jl b/lib/QECCore/src/QECCore.jl index 56e31d9c9..7009aa98f 100644 --- a/lib/QECCore/src/QECCore.jl +++ b/lib/QECCore/src/QECCore.jl @@ -14,11 +14,11 @@ rate, metacheck_matrix_x, metacheck_matrix_z, metacheck_matrix, bivariate_bicycl generator_polynomial export AbstractECC, AbstractQECC, AbstractCECC, AbstractCSSCode, AbstractDistanceAlg -# FactoredBitNoiseModel -export FactoredBitNoiseModel, depolarization_error_model, isvector, isindependent, IndependentVectorSampler, BitStringSamples +# Decoding interfaces +export AbstractDecoder, AbstractDecodingResult, AbstractDecodingProblem, AbstractNoiseModel, AbstractDecodingScenario, decode, sample, decoding_error_rate, AbstractSyndrome, syndrome -# Decoding -export AbstractDecoder, decode, sample, decoding_error_rate, DetectorModelProblem, check_decoding_result +# DetectorModelProblem +export DetectorModelProblem, FactoredBitNoiseModel, depolarization_error_model, isvector, isindependent, IndependentVectorSampler, BitStringSamples, MatrixDecodingResult, MatrixSyndrome # QEC Codes export Perfect5, Cleve8, Gottesman diff --git a/lib/QECCore/src/decoding_impl.jl b/lib/QECCore/src/decoding_impl.jl index 004bad31f..1698e5cd7 100644 --- a/lib/QECCore/src/decoding_impl.jl +++ b/lib/QECCore/src/decoding_impl.jl @@ -164,19 +164,25 @@ function measure_syndrome(check_matrix::AbstractMatrix, error_patterns::Abstract end measure_syndrome(problem::DetectorModelProblem, error_patterns::Matrix{Bool}) = measure_syndrome(problem.check_matrix, error_patterns) -syndrome(samples::BitStringSamples) = samples.check_bits +struct MatrixSyndrome <: AbstractSyndrome + mat::Matrix{Bool} +end +syndrome(samples::BitStringSamples) = MatrixSyndrome(samples.check_bits) +struct MatrixDecodingResult <: AbstractDecodingResult + mat::Matrix{Bool} +end """ $TYPEDSIGNATURES Check if the error pattern is a logical error. """ -function decoding_error_rate(problem::DetectorModelProblem, samples::BitStringSamples, decoding_result::AbstractMatrix) - ep = decoding_result - samples.physical_bits - return count(check_decoding_result(problem, ep)) / size(decoding_result, 2) +function decoding_error_rate(problem::DetectorModelProblem, samples::BitStringSamples, decoding_result::MatrixDecodingResult) + ep = Bool.(mod.(decoding_result.mat - samples.physical_bits, 2)) + return count(check_decoding_result(problem, ep)) / size(decoding_result.mat, 2) end -function check_decoding_result(problem::DetectorModelProblem,decoding_result_diff::AbstractMatrix) +function check_decoding_result(problem::DetectorModelProblem,decoding_result_diff::AbstractMatrix{Bool}) syndrome_test = any(measure_syndrome(problem.check_matrix, decoding_result_diff), dims=1) logical_test = any(measure_syndrome(problem.logical_matrix, decoding_result_diff), dims=1) return syndrome_test .|| logical_test diff --git a/lib/QECCore/src/decoding_interface.jl b/lib/QECCore/src/decoding_interface.jl index 39e18e40e..f7cb86663 100644 --- a/lib/QECCore/src/decoding_interface.jl +++ b/lib/QECCore/src/decoding_interface.jl @@ -49,7 +49,7 @@ Extract the syndrome from the samples. function syndrome end abstract type AbstractDecoder end - +abstract type AbstractDecodingResult end """ decode(problem::AbstractDecodingProblem, syndrome::AbstractSyndrome, decoder::AbstractDecoder) @@ -61,19 +61,19 @@ Decode the syndrome using the decoder. - `decoder::AbstractDecoder`: The decoder to use. ### Outputs -- `decoding_result::AbstractMatrix`: The decoded result. +- `decoding_result::AbstractDecodingResult`: The decoded result. """ function decode end """ - decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractMatrix) + decoding_error_rate(problem::AbstractDecodingProblem, samples::AbstractDecodingSamples, decoding_result::AbstractDecodingResult) Calculate the error rate of the decoding result. ### Inputs - `problem::AbstractDecodingProblem`: The decoding problem to validate. - `syndrome::AbstractSyndrome`: The syndrome to validate. -- `decoding_result::AbstractMatrix`: The decoded result to validate. +- `decoding_result::AbstractDecodingResult`: The decoded result to validate. ### Outputs - `rate::Float64`: The error rate of the decoding result. diff --git a/lib/QECCore/test/test_decoding.jl b/lib/QECCore/test/test_decoding.jl index 378815829..57a6388d7 100644 --- a/lib/QECCore/test/test_decoding.jl +++ b/lib/QECCore/test/test_decoding.jl @@ -66,30 +66,30 @@ @test QECCore.measure_syndrome(problem, ep) == Bool[true; true; true; false; false; false;;] ep2 = copy(ep) - @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test !(QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) ep2[1] = true - @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test (QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) # applying a logical operator will lead to a logical error ep[2] = true ep[3] = true - @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test (QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) ep2 = copy(ep) ep2[8:10] .= true - @test (QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test (QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) # applying a stabilizer will not lead to a logical error ep2 = copy(ep) ep2[4:6] .= true ep2[7] = false - @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test !(QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) ep2 = copy(ep) ep2[11:14] .= true - @test !(QECCore.check_decoding_result(problem, ep2.-ep)[]) + @test !(QECCore.check_decoding_result(problem, Bool.(mod.(ep2.-ep, 2)))[]) samples = sample(problem, 1000, IndependentVectorSampler()) - @test decoding_error_rate(problem, samples, samples.physical_bits) == 0.0 + @test decoding_error_rate(problem, samples, MatrixDecodingResult(samples.physical_bits)) == 0.0 end end From 139f73f042408150bbc57560e2785b0debd10095 Mon Sep 17 00:00:00 2001 From: nzy Date: Tue, 16 Sep 2025 16:25:27 +0800 Subject: [PATCH 8/8] change log --- CHANGELOG.md | 1 + lib/QECCore/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3de21653..345bc1c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## v0.10.1-dev +- Add decoding pipeline in `QECCore`. - The `TrivariateTricycleCode` is implemented using a novel realization via `Oscar.jl`'s multivariate polynomial quotient ring formalism in the ECC submodule. - The `GeneralizedToricCode` on twisted tori via `Oscar.jl`'s Laurent polynomials is now implemented in the ECC submodule. - The `HomologicalProductCode` and `DoubleHomologicalProductCode` are now implemented via `Oscar.jl`'s homological algebra in the ECC submodule. diff --git a/lib/QECCore/CHANGELOG.md b/lib/QECCore/CHANGELOG.md index 25f0a8dc3..f8cd0f767 100644 --- a/lib/QECCore/CHANGELOG.md +++ b/lib/QECCore/CHANGELOG.md @@ -2,6 +2,7 @@ ## v0.1.2 - dev +- Add decoding pipeline in `QECCore`. - Add novel `[[n² + m²,(n - rank([C ∣ M]))² + (m − rank([C ∣ M]ᵀ))², d]]` quantum Tillich-Zémor `random_TillichZemor_code` codes to `QECCore` and introduce `QECCoreNemoExt` for accurate matrix `rank` computation. - Introduce `metacheck_matrix_x`, `metacheck_matrix_z`, and `metacheck_matrix` for CSS codes built using chain complexes and homology. - Move the following codes from `QuantumClifford.ECC` to `QECCore`: `ReedMuller`, `RecursiveReedMuller`, `QuantumReedMuller`, `Hamming`, `Golay`, `Triangular488 `, `Triangular666 `, `Gottesman`.