diff --git a/ext/QuantumCliffordJuMPExt/min_distance_mixed_integer_programming.jl b/ext/QuantumCliffordJuMPExt/min_distance_mixed_integer_programming.jl index 81409a3dc..cee6a7615 100644 --- a/ext/QuantumCliffordJuMPExt/min_distance_mixed_integer_programming.jl +++ b/ext/QuantumCliffordJuMPExt/min_distance_mixed_integer_programming.jl @@ -10,7 +10,7 @@ even overlap with each X-check of the stabilizer binary representation `stab`. an odd overlap with logical-X operator `logicOp` on the i-th logical qubit. Specifically, it calculates the minimum Hamming weight \$d_{Z}\$ for the Z-type -logical operator, and the minimum distance for X-type logical operators is the same. +logical operator. The minimum distance for X-type logical operators is the same. ### Background on Minimum Distance @@ -59,9 +59,9 @@ julia> distance(Steane7()) 3 ``` -The minimum distance problem for quantum codes is *NP-hard*, and this hardness extends -to multiplicative and additive approximations, even when restricted to stabilizer or -CSS codes, with the result established through a reduction from classical problems in +!!! note The minimum distance problem for quantum codes is *NP-hard*, and this hardness +extends to multiplicative and additive approximations, even when restricted to stabilizer +or CSS codes, with the result established through a reduction from classical problems in the CWS framework using a 4-cycle free graph [kapshikar2023hardness](@cite). Despite this, methods that improve on brute-force approaches are actively explored. @@ -136,6 +136,22 @@ julia> code_n(c1), code_k(c1), distance(c1) (48, 6, 8) ``` +!!!Since the [[48, 6, 8]] GB code does not have specific lower and upper bounds +(e.g., consider [[48, 6, 5 ≤ d ≤ 8]]), the minimum distance for all `Z`-type and +`X`-type logical qubits remains the same. In this context, the *exact* minimum +distance is provided. + +```jldoctest examples +julia> distance(c1, all_logical_qubits=true) +Dict{Int64, Int64} with 6 entries: + 5 => 8 + 4 => 8 + 6 => 8 + 2 => 8 + 3 => 8 + 1 => 8 +``` + ### Applications - The first usecase of the MIP approach was the code capacity Most Likely @@ -147,25 +163,23 @@ to a mixed integer linear program and using the GNU Linear Programming Kit. the code distance was calculated using the mixed integer programming approach. """ -function distance(c::Stabilizer; num_logical_qubits=code_k(c), upper_bound=false) - lx, _ = get_lx_lz(c) - H = stab_to_gf2(parity_checks(c)) - H = SparseMatrixCSC{Int, Int}(H) - hx = get_stab_hx(H) - 1 <= num_logical_qubits < code_k(c) && return _minimum_distance(hx, lx[num_logical_qubits, :]) # for large instances - 1 <= num_logical_qubits <= code_k(c) || throw(ArgumentError("The number of logical qubits must be between 1 and $(code_k(c)) inclusive")) - if num_logical_qubits == code_k(c) - weights = [] - for i in 1:num_logical_qubits - w = _minimum_distance(hx, lx[num_logical_qubits, :]) - push!(weights, w) - end - if upper_bound - return minimum(weights), maximum(weights) # return upper bound of minimum distance if required. - else - return minimum(weights) - end +function distance(c::Stabilizer; upper_bound=false, logical_qubit=code_k(c), all_logical_qubits=false, logical_operator_type=:X) + 1 <= logical_qubit <= code_k(c) || throw(ArgumentError("The number of logical qubit must be between 1 and $(code_k(c)) inclusive")) + logical_operator_type == :X || logical_operator_type == :Z || throw(ArgumentError("Invalid type of logical operator: Use :X or :Z")) + l = get_lx_lz(c)[1] + H = SparseMatrixCSC{Int, Int}(stab_to_gf2(parity_checks(c))) + h = get_stab(H, :X) + if logical_operator_type == :Z + l = get_lx_lz(c)[2] + H = SparseMatrixCSC{Int, Int}(stab_to_gf2(parity_checks(c))) + h = get_stab(H, :Z) + end + weights = Dict{Int, Int}() + for i in 1:logical_qubit + w = _minimum_distance(h, l[i, :]) + weights[i] = w end + return upper_bound ? maximum(values(weights)) : (all_logical_qubits ? weights : minimum(values(weights))) end # Computing minimum distance for quantum LDPC codes is a NP-Hard problem, diff --git a/ext/QuantumCliffordJuMPExt/util.jl b/ext/QuantumCliffordJuMPExt/util.jl index 35c79dd63..bcd77f0ce 100644 --- a/ext/QuantumCliffordJuMPExt/util.jl +++ b/ext/QuantumCliffordJuMPExt/util.jl @@ -1,10 +1,14 @@ -function get_stab_hx(matrix::SparseMatrixCSC{Int, Int}) +function get_stab(matrix::SparseMatrixCSC{Int, Int}, logical_operator_type::Symbol) rows, cols = size(matrix) - rhs_start = div(cols, 2) + 1 - rhs_cols = matrix[:, rhs_start:cols] - non_zero_rows_rhs = unique(rhs_cols.rowval) - zero_rows_rhs = setdiff(1:rows, non_zero_rows_rhs) - return matrix[zero_rows_rhs, :] + if logical_operator_type == :Z + col_range = 1:div(cols, 2) + else logical_operator_type == :X + col_range = div(cols, 2) + 1:cols + end + submatrix = matrix[:, col_range] + non_zero_rows = unique(submatrix.rowval) + zero_rows = setdiff(1:rows, non_zero_rows) + return matrix[zero_rows, :] end function get_lx_lz(c::Stabilizer) diff --git a/test/test_ecc_coprime_bivaraite_bicycle.jl b/test/test_ecc_coprime_bivaraite_bicycle.jl index 6e82637ad..270683a94 100644 --- a/test/test_ecc_coprime_bivaraite_bicycle.jl +++ b/test/test_ecc_coprime_bivaraite_bicycle.jl @@ -2,8 +2,10 @@ using Nemo using Nemo: gcd using Hecke + using JuMP + using GLPK using Hecke: group_algebra, GF, abelian_group, gens - using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n + using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n, distance @testset "Reproduce Table 2 wang2024coprime" begin # [[30,4,6]] @@ -14,6 +16,7 @@ B = 𝜋 + 𝜋^3 + 𝜋^8 c = two_block_group_algebra_codes(A, B) @test gcd([l,m]) == 1 + @test distance(c) == 6 @test code_n(c) == 30 && code_k(c) == 4 # [[42,6,6]] @@ -24,6 +27,7 @@ B = 𝜋 + 𝜋^3 + 𝜋^11 c = two_block_group_algebra_codes(A, B) @test gcd([l,m]) == 1 + @test distance(c) == 6 @test code_n(c) == 42 && code_k(c) == 6 # [[70,6,8]] @@ -34,6 +38,7 @@ B = 1 + 𝜋 + 𝜋^12; c = two_block_group_algebra_codes(A, B) @test gcd([l,m]) == 1 + @test distance(c) == 8 @test code_n(c) == 70 && code_k(c) == 6 # [[108,12,6]] diff --git a/test/test_ecc_generalized_bicycle_codes.jl b/test/test_ecc_generalized_bicycle_codes.jl index c6e1f50bf..3751944ab 100644 --- a/test/test_ecc_generalized_bicycle_codes.jl +++ b/test/test_ecc_generalized_bicycle_codes.jl @@ -1,18 +1,32 @@ @testitem "ECC GB" begin using Hecke - using QuantumClifford.ECC: generalized_bicycle_codes, code_k, code_n + using JuMP + using GLPK + using QuantumClifford.ECC: generalized_bicycle_codes, code_k, code_n, distance # codes taken from Table 1 of [lin2024quantum](@cite) # Abelian 2BGA codes can be viewed as GB codes. - @test code_n(generalized_bicycle_codes([0 , 15, 16, 18], [0 , 1, 24, 27], 35)) == 70 - @test code_k(generalized_bicycle_codes([0 , 15, 16, 18], [0 , 1, 24, 27], 35)) == 8 - @test code_n(generalized_bicycle_codes([0 , 1, 3, 7], [0 , 1, 12, 19], 27)) == 54 - @test code_k(generalized_bicycle_codes([0 , 1, 3, 7], [0 , 1, 12, 19], 27)) == 6 - @test code_n(generalized_bicycle_codes([0 , 10, 6, 13], [0 , 25, 16, 12], 30)) == 60 - @test code_k(generalized_bicycle_codes([0 , 10, 6, 13], [0 , 25, 16, 12], 30)) == 6 - @test code_n(generalized_bicycle_codes([0 , 9, 28, 31], [0 , 1, 21, 34], 36)) == 72 - @test code_k(generalized_bicycle_codes([0 , 9, 28, 31], [0 , 1, 21, 34], 36)) == 8 - @test code_n(generalized_bicycle_codes([0 , 9, 28, 13], [0 , 1, 21, 34], 36)) == 72 - @test code_k(generalized_bicycle_codes([0 , 9, 28, 13], [0 , 1, 3, 22], 36)) == 10 + + codes = [generalized_bicycle_codes([0 , 1, 3, 7], [0 , 1, 12, 19], 27), # [[54, 6, 9]] + generalized_bicycle_codes([0 , 10, 6, 13], [0 , 25, 16, 12], 30), # [[60, 6, 10]] + generalized_bicycle_codes([0 , 15, 16, 18], [0 , 1, 24, 27], 35), # [[70, 8, 10]] + generalized_bicycle_codes([0 , 9, 28, 31], [0 , 1, 21, 34], 36), # [[72, 8, 10]] + generalized_bicycle_codes([0 , 9, 28, 13], [0 , 1, 3, 22], 36)] # [[72, 8, 9]] + + n_values = [54, 60, 70, 72, 72] + k_values = [6 , 6 , 8 , 8 , 10] + d_values = [9 , 10, 10, 10, 9] + + for i in 1:length(n_values) + @test code_n(codes[i]) == n_values[i] + end + + for i in 1:length(n_values) + @test code_k(codes[i]) == k_values[i] + end + + for i in 1:length(d_values) + j = rand(1:code_k(codes[i])) + @test distance(codes[i], logical_qubit=j) == d_values[i] end end