diff --git a/docs/src/references.bib b/docs/src/references.bib index aede4fae9..1f05888ee 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -1209,3 +1209,13 @@ @article{dennis2002topological year={2002}, publisher={American Institute of Physics} } + +@misc{steffan2025tilecodeshighefficiencyquantum, + title={Tile Codes: High-Efficiency Quantum Codes on a Lattice with Boundary}, + author={Vincent Steffan and Shin Ho Choe and Nikolas P. Breuckmann and Francisco Revson Fernandes Pereira and Jens Niklas Eberhardt}, + year={2025}, + eprint={2504.09171}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + url={https://arxiv.org/abs/2504.09171}, +} diff --git a/lib/QECCore/src/QECCore.jl b/lib/QECCore/src/QECCore.jl index e8d8be1ad..6d0dddd86 100644 --- a/lib/QECCore/src/QECCore.jl +++ b/lib/QECCore/src/QECCore.jl @@ -20,7 +20,7 @@ export Perfect5, Cleve8, Gottesman # CSS Codes export Toric, Bitflip3, Phaseflip3, Shor9, Steane7, Surface, CSS, QuantumReedMuller, Triangular488, Triangular666, DelfosseReichardt, DelfosseReichardtRep, DelfosseReichardt823, QuantumTannerGraphProduct, CyclicQuantumTannerGraphProduct, -TillichZemor, random_TillichZemor_code, BivariateBicycleViaCirculantMat +TillichZemor, random_TillichZemor_code, BivariateBicycleViaCirculantMat, Tile2D # Classical Codes export RepCode, ReedMuller, RecursiveReedMuller, Golay, Hamming, random_Gallager_ldpc, Goppa, random_Goppa_code @@ -52,6 +52,7 @@ include("codes/quantum/color_codes.jl") include("codes/quantum/quantumtannergraphproduct.jl") include("codes/quantum/tillichzemor.jl") include("codes/quantum/generalized_circulant_bivariate_bicycle.jl") +include("codes/quantum/tile2d.jl") # Reed-Muller Codes include("codes/classical/reedmuller.jl") diff --git a/lib/QECCore/src/codes/quantum/tile2d.jl b/lib/QECCore/src/codes/quantum/tile2d.jl new file mode 100644 index 000000000..6895eb1db --- /dev/null +++ b/lib/QECCore/src/codes/quantum/tile2d.jl @@ -0,0 +1,128 @@ +""" +2D Tile is a generalization of surface codes that offers flexibility in terms of locality and stabilizer check weight +without compromising on the 2D locality of the 2D surface code. It encodes more logical qubits than surface code, and +and provides O(1)-locality. + +```jldoctest +julia> using QuantumClifford; using QuantumClifford.ECC; # hide + +julia> B = 3; + +julia> horizX = [(0,0),(2,1),(2,2)]; + +julia> vertX = [(0,2),(1,2),(2,0)]; + +julia> Lx, Ly = 10, 10; + +julia> c = Tile2D(B, horizX, vertX, Lx, Ly); + +julia> code_n(c), code_k(c) +(288, 8) +``` +""" +struct Tile2D <: AbstractCSSCode + """Size of the tile box ``(B \\times B)`` determining the support of a stabilizer.""" + B::Int + """Positions of horizontal edges within the tile box.""" + horiz::Vector{Tuple{Int,Int}} + """Positions of vertical edges within the tile box.""" + vert::Vector{Tuple{Int,Int}} + """Number of tiles along the x-direction.""" + Lx::Int + """Number of tiles along the y-direction.""" + Ly::Int + + function Tile2D(B::Int, horiz::Vector{Tuple{Int,Int}}, vert::Vector{Tuple{Int,Int}}, Lx::Int, Ly::Int) + new(B, horiz, vert, Lx, Ly) + end +end + +function _rectangular_layout(tile::Tile2D) + black = Set{Tuple{Int,Int}}() + red = Set{Tuple{Int,Int}}() + blue = Set{Tuple{Int,Int}}() + B, Lx, Ly = tile.B, tile.Lx, tile.Ly + for x in 0:Lx-1, y in 0:Ly-1 + push!(black, (x,y)) + end + for x in 0:Lx-1, t in 1:B-1 + push!(red, (x, -t)) + push!(red, (x, Ly-1+t)) + end + for y in 0:Ly-1, t in 1:B-1 + push!(blue, (-t, y)) + push!(blue, (Lx-1+t, y)) + end + return black, red, blue +end + +function _complement_tile(tile::Tile2D) + B = tile.B + horiz_z = [(B-1-x, B-1-y) for (x,y) in tile.vert] + vert_z = [(B-1-x, B-1-y) for (x,y) in tile.horiz] + Tile2D(B, horiz_z, vert_z, tile.Lx, tile.Ly) +end + +function _physical_qubits(tile::Tile2D) + qubits = Set{Tuple{Symbol,Int,Int}}() + black, _, _ = _rectangular_layout(tile) + for (vx,vy) in black + for x in 0:tile.B-1, y in 0:tile.B-1 + push!(qubits, (:h, vx+x, vy+y)) + push!(qubits, (:v, vx+x, vy+y)) + end + end + return qubits +end + + +function _edges((vx,vy)::Tuple{Int,Int}, tile::Tile2D) + edges = Tuple{Symbol,Int,Int}[] + for (x,y) in tile.horiz + push!(edges, (:h, vx+x, vy+y)) + end + for (x,y) in tile.vert + push!(edges, (:v, vx+x, vy+y)) + end + return edges +end + +function parity_matrix_xz(tile::Tile2D) + tileZ = _complement_tile(tile) + # "We will always restrict ourselves to (rotated) rectangular shapes" [steffan2025tilecodeshighefficiencyquantum](@cite). + black, red, blue = _rectangular_layout(tile) + physical = _physical_qubits(tile) + Xrows = Vector{Vector{Tuple{Symbol,Int,Int}}}() + Zrows = Vector{Vector{Tuple{Symbol,Int,Int}}}() + # "We fine-tune the layout to the specific support of the stabilizers. First remove + # all qubits that are not supported in any X-type stabilizer or are not supported + # in any Z-type stabilizer" [steffan2025tilecodeshighefficiencyquantum](@cite). + for v in black + push!(Xrows, filter(in(physical), _edges(v, tile))) + push!(Zrows, filter(in(physical), _edges(v, tileZ))) + end + for v in red + push!(Xrows, filter(in(physical), _edges(v, tile))) + end + for v in blue + push!(Zrows, filter(in(physical), _edges(v, tileZ))) + end + # "Finally, we remove all stabilizers whose support has become empty because of aforementioned procedure" [steffan2025tilecodeshighefficiencyquantum](@cite). + filter!(!isempty, Xrows) + filter!(!isempty, Zrows) + qubits = unique(vcat(Xrows..., Zrows...)) + qindex = Dict(q => i for (i,q) in enumerate(qubits)) + Hx = spzeros(Int, length(Xrows), length(qubits)) + Hz = spzeros(Int, length(Zrows), length(qubits)) + for (i,row) in enumerate(Xrows), q in row + Hx[i, qindex[q]] = 1 + end + for (i,row) in enumerate(Zrows), q in row + Hz[i, qindex[q]] = 1 + end + return Hx, Hz +end + +parity_matrix_x(tile::Tile2D) = parity_matrix_xz(tile)[1] + +parity_matrix_z(tile::Tile2D) = parity_matrix_xz(tile)[2] diff --git a/lib/QECCore/test/codes/tile2d.jl b/lib/QECCore/test/codes/tile2d.jl new file mode 100644 index 000000000..7d696d98e --- /dev/null +++ b/lib/QECCore/test/codes/tile2d.jl @@ -0,0 +1,66 @@ +@testitem "Tile 2D" begin + using Test + using Nemo: matrix, GF, rank + using QECCore: Tile2D + using QuantumClifford: stab_looks_good, stab_to_gf2 + using QuantumClifford.ECC: parity_checks, code_n, code_k, parity_matrix_x, parity_matrix_z + + @testset "Tile 2D" begin + # from table 1 of https://arxiv.org/pdf/2504.09171 + table_I = [ + (288, 8, 3, [(0,0),(2,1),(2,2)], [(0,2),(1,2),(2,0)], 10, 10), # [[288, 8, 12]] + (288, 8, 3, [(0,0),(2,0),(0,1),(0,2)], [(0,0),(0,2),(1,1),(2,2)], 10, 10), # [[288, 8, 14]] + (288, 18, 4, [(0,0),(0,3),(2,2),(3,0)], [(0,1),(1,0),(1,1),(3,3)], 9, 9), # [[288, 18, 13]] + (512, 18, 4, [(0,0),(0,3),(2,2),(3,0)], [(0,1),(1,0),(1,1),(3,3)], 13, 13)] # [[512, 18, 19]] + + for (n, k, B, horiz, vert, Lx, Ly) in table_I + c = Tile2D(B, horiz, vert, Lx, Ly) + stab = parity_checks(c) + nₛ, kₛ = code_n(stab), code_k(stab) + H = stab_to_gf2(stab) + mat = matrix(GF(2), H) + computed_rank = rank(mat) + @test computed_rank == nₛ - kₛ + @test stab_looks_good(stab, remove_redundant_rows=true) + @test computed_rank == n - k && computed_rank == nₛ - kₛ && n == nₛ && k == kₛ + end + + # check-weight tests + # From Table I of https://arxiv.org/pdf/2504.09171v1 + # [[288, 8, 12]] + B = 3 + horizX = [(0,0),(2,1),(2,2)] + vertX = [(0,2),(1,2),(2,0)] + Lx, Ly = 10, 10 + c = Tile2D(B, horizX, vertX, Lx, Ly) + @test all(maximum(sum(Matrix(parity_matrix_z(c)), dims=2)) .== 6) + @test all(maximum(sum(Matrix(parity_matrix_x(c)), dims=2)) .== 6) + + # [[288, 8, 14]] + B = 3 + horizX = [(0,0),(2,0),(0,1),(0,2)] + vertX = [(0,0),(0,2),(1,1),(2,2)] + Lx, Ly = 10, 10 + c = Tile2D(B, horizX, vertX, Lx, Ly) + @test all(maximum(sum(Matrix(parity_matrix_z(c)), dims=2)) .== 8) + @test all(maximum(sum(Matrix(parity_matrix_x(c)), dims=2)) .== 8) + + # [[288, 18, 13]] + B = 4 + horizX = [(0,0),(0,3),(2,2),(3,0)] + vertX = [(0,1),(1,0),(1,1),(3,3)] + Lx, Ly = 9, 9 + c = Tile2D(B, horizX, vertX, Lx, Ly) + @test all(maximum(sum(Matrix(parity_matrix_z(c)), dims=2)) .== 8) + @test all(maximum(sum(Matrix(parity_matrix_x(c)), dims=2)) .== 8) + + # [[512, 18, 19]] + B = 4 + horizX = [(0,0),(0,3),(2,2),(3,0)] + vertX = [(0,1),(1,0),(1,1),(3,3)] + Lx, Ly = 13, 13 + c = Tile2D(B, horizX, vertX, Lx, Ly) + @test all(maximum(sum(Matrix(parity_matrix_z(c)), dims=2)) .== 8) + @test all(maximum(sum(Matrix(parity_matrix_x(c)), dims=2)) .== 8) + end +end diff --git a/lib/QECCore/test/test_codes.jl b/lib/QECCore/test/test_codes.jl index c55767264..26d0a2da4 100644 --- a/lib/QECCore/test/test_codes.jl +++ b/lib/QECCore/test/test_codes.jl @@ -90,4 +90,8 @@ @testset "Circulant Bivariate Bicycle Codes" begin include("codes/generalized_circulant_bivariate_bicycle.jl") end + + @testset "Tile 2D" begin + include("codes/tile2d.jl") + end end diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 96add4ebb..d9c8bb7ea 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -44,7 +44,7 @@ export parity_checks, parity_matrix_x, parity_matrix_z, iscss, GeneralizedBicycle, ExtendedGeneralizedBicycle, HomologicalProduct, DoubleHomologicalProduct, GeneralizedToric, TrivariateTricycle, BivariateBicycleViaPoly, - MultivariateMulticycle, + MultivariateMulticycle, Tile2D, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, TableDecoder, CSSTableDecoder,