From f3eb7cd7fd352fcc6f4994b7e42b7feca8112b7b Mon Sep 17 00:00:00 2001 From: IsaacP1234 <111547953+IsaacP1234@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:46:50 -0400 Subject: [PATCH] Additional group theory functions for error correction (#351) --------- Co-authored-by: Kenneth Goodenough Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 6 + docs/src/references.bib | 25 +++ src/QuantumClifford.jl | 1 + src/grouptableaux.jl | 296 ++++++++++++++++++++++++++++++++++-- src/mul_leftright.jl | 6 + src/project_trace_reset.jl | 1 + test/test_group_tableaux.jl | 77 +++++++--- 7 files changed, 380 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06856e122..2fc286752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ ## v0.9.13 - dev +- New error correction group theory tools: + - `canonicalize_noncomm` function to find a generating set with minimal anticommutivity + - `SubsystemCodeTableau` data structure to represent the output of `canonicalize_noncomm` + - `commutify` function to find a commutative version of a non-commutative set of Paulis with minimal changes + - `matroid_parent` to, for set of Paulis that doesn't represent a state, find a version + that does. - Implementing additional named two-qubit gates: `sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, sSQRTZZ, sInvSQRTZZ` diff --git a/docs/src/references.bib b/docs/src/references.bib index 29500a034..281ace9c1 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -403,6 +403,31 @@ @inproceedings{brown2013short doi = {10.1109/ISIT.2013.6620245} } +@article{RevModPhys.87.307, + title = {Quantum error correction for quantum memories}, + author = {Terhal, Barbara M.}, + journal = {Rev. Mod. Phys.}, + volume = {87}, + issue = {2}, + pages = {307--346}, + numpages = {40}, + year = {2015}, + month = {Apr}, + publisher = {American Physical Society}, + doi = {10.1103/RevModPhys.87.307}, + url = {https://link.aps.org/doi/10.1103/RevModPhys.87.307} +} + +@misc{goodenough2024bipartiteentanglementnoisystabilizer, + title={Bipartite entanglement of noisy stabilizer states through the lens of stabilizer codes}, + author={Kenneth Goodenough and Aqil Sajjad and Eneet Kaur and Saikat Guha and Don Towsley}, + year={2024}, + eprint={2406.02427}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + url={https://arxiv.org/abs/2406.02427}, +} + @article{panteleev2021degenerate, title = {Degenerate {{Quantum LDPC Codes With Good Finite Length Performance}}}, author = {Panteleev, Pavel and Kalachev, Gleb}, diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6837483cb..9d892c958 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -78,6 +78,7 @@ export graphstate, graphstate!, graph_gatesequence, graph_gate, # Group theory tools groupify, minimal_generating_set, pauligroup, normalizer, centralizer, contractor, delete_columns, + canonicalize_noncomm, commutify, matroid_parent, SubsystemCodeTableau, # Clipped Gauge canonicalize_clip!, bigram, entanglement_entropy, # mctrajectories diff --git a/src/grouptableaux.jl b/src/grouptableaux.jl index 208f1ddfb..6f76802cf 100644 --- a/src/grouptableaux.jl +++ b/src/grouptableaux.jl @@ -1,3 +1,110 @@ +""" +A tableau representation of the non-commutative canonical form of a set of Paulis, +which is used in [`commutify`](@ref). + +They are organized in the same form as [`MixedDestabilizer`](@ref) +with a stabilizer, destabilizer, logical X, and logical Z components. +""" +mutable struct SubsystemCodeTableau{T <: Tableau} <: AbstractStabilizer + tab::T + index::Int + r::Int + m::Int + k::Int +end + +function SubsystemCodeTableau(t::Tableau) + index = 1 + for i in range(1, stop=length(t), step=2) + if i + 1 > length(t) + break + end + if comm(t[i], t[i+1]) == 0x01 + index = i+2 # index to split t into non-commuting pairs and commuting operators + end + end + s = Stabilizer(t[index:length(t)]) + ind = 1 + if length(s)>nqubits(s)#if stabilizer is overdetermined, Destabilizer constructor throws error + m = length(s) + tab = zero(Tableau, length(t)+length(s), nqubits(t)) + for i in s + tab[ind] = zero(PauliOperator, nqubits(s)) + ind+=1 + end + else + d = Destabilizer(s) + m = length(d) + tab = zero(Tableau, length(t)+length(d), nqubits(t)) + for p in destabilizerview(d) + tab[ind] = p + ind+=1 + end + end + for i in range(1, stop=index-1, step=2) + tab[ind] = t[i] + ind+=1 + end + for p in s + tab[ind] = p + ind+=1 + end + for i in range(2, stop=index, step=2) + tab[ind] = t[i] + ind+=1 + end + return SubsystemCodeTableau(tab, index, length(s), m, Int((index-1)/2)) +end + + +Base.copy(t::SubsystemCodeTableau) = SubsystemCodeTableau(copy(t.tab), t.index, t.r, t.m, t.k) + +function Base.show(io::IO, t::SubsystemCodeTableau) + r = t.r+t.m+2*t.k + q = nqubits(t) + if get(io, :compact, false) | haskey(io, :typeinfo) + print(io, "MixedDestablizer $rร—$q") + elseif get(io, :limit, false) + h,w = displaysize(io) + println(io, "๐’Ÿโ„ฏ๐“ˆ๐“‰๐’ถ๐’ท" * "โ”"^max(min(w-9,size(t.tab,2)-4),0)) + _show(io, destabilizerview(t).tab, w, hรท4) + if r != q + println(io) + println(io, "๐’ณ" * "โ”"^max(min(w-5,size(t.tab,2)),0)) + _show(io, logicalxview(t).tab, w, hรท4) + end + println(io) + println(io, "๐’ฎ๐“‰๐’ถ๐’ท" * "โ”"^max(min(w-7,size(t.tab,2)-2),0)) + _show(io, stabilizerview(t).tab, w, hรท4) + if r != q + println(io) + println(io, "๐’ต" * "โ”"^max(min(w-5,size(t.tab,2)),0)) + _show(io, logicalzview(t).tab, w, hรท4) + end + else + println(io, "๐’Ÿโ„ฏ๐“ˆ๐“‰๐’ถ๐’ท" * "โ”"^max(size(t.tab,2)-4,0)) + _show(io, destabilizerview(t).tab, missing, missing) + if r != q + println(io) + println(io, "๐’ณโ‚—" * "โ”"^max(size(t.tab,2),0)) + _show(io, logicalxview(t).tab, missing, missing) + end + println(io) + println(io, "๐’ฎ๐“‰๐’ถ๐’ท" * "โ”"^max(size(t.tab,2)-2,0)) + _show(io, stabilizerview(t).tab, missing, missing) + if r != q + println(io) + println(io, "๐’ตโ‚—" * "โ”"^max(size(t.tab,2))) + _show(io, logicalzview(t).tab, missing, missing) + end + end +end + +@inline stabilizerview(s::SubsystemCodeTableau) = Stabilizer(@view tab(s)[s.m+s.k+1:s.m+s.k+s.r]) +@inline destabilizerview(s::SubsystemCodeTableau) = Stabilizer(@view tab(s)[1:s.r]) +@inline logicalxview(s::SubsystemCodeTableau) = Stabilizer(tab(s)[s.m+1:s.m+s.k]) +@inline logicalzview(s::SubsystemCodeTableau) = Stabilizer(tab(s)[length(s.tab)-s.k+1:length(s.tab)]) + """ Return the full stabilizer group represented by the input generating set (a [`Stabilizer`](@ref)). @@ -11,16 +118,20 @@ julia> groupify(S"XZ ZX") + YY ``` """ -function groupify(s::Stabilizer) +function groupify(s::Stabilizer; phases=false) # Create a `Tableau` of 2โฟ n-qubit identity Pauli operators(where n is the size of # `Stabilizer` s), then multiply each one by a different subset of the elements in s to # create all 2โฟ unique elements in the group generated by s, then return the `Tableau`. - n = length(s)::Int - group = zero(Tableau, 2^n, nqubits(s)) + if phases == true + throw(ArgumentError("in groupify phases=true functionality not yet implemented")) + end + gen_set = minimal_generating_set(s) + n = length(gen_set)::Int + group = zero(Tableau, 2^n, nqubits(gen_set)) for i in 0:2^n-1 for (digit_order, j) in enumerate(digits(i, base=2, pad=n)) if j == 1 - group[i+1] *= s[digit_order] + mul_left!(group, i+1, tab(gen_set), digit_order, phases=Val(false)) end end end @@ -48,7 +159,7 @@ function minimal_generating_set(s::Stabilizer) if r == 0 gs = zero(Stabilizer, 1, nqubits(s)) if 0x02 in phases(s) - gs[1] = -1 * gs[1] + phases(gs)[1] = 0x2 end return gs else @@ -56,6 +167,153 @@ function minimal_generating_set(s::Stabilizer) end end +""" +For a not-necessarily commutative set of Paulis, return a generating set of the form +โŸจAโ‚, Aโ‚‚, ... Aโ‚–, Aโ‚–โ‚Šโ‚, ... Aโ‚˜, Bโ‚, Bโ‚‚, ... Bโ‚–โŸฉ where pairs Aโ‚–, Bโ‚– anticommute and all other pairings commute. Based on [RevModPhys.87.307](@cite) + +Returns the generating set as a data structure of type [`SubsystemCodeTableau`](@ref). The +`logicalxview` function returns the โŸจAโ‚, Aโ‚‚,... Aโ‚–โŸฉ, and the `logicalzview` +function returns โŸจBโ‚, Bโ‚‚, ... Bโ‚–โŸฉ. `stabilizerview` returns โŸจAโ‚–โ‚Šโ‚, ... Aโ‚˜โŸฉ +as a Stabilizer, and `destabilizerview` returns the Destabilizer of that Stabilizer. + +Phases are zeroed-out in this canonicalization. + +```jldoctest +julia> canonicalize_noncomm(T"XX XZ XY") +๐’Ÿโ„ฏ๐“ˆ๐“‰๐’ถ๐’ท ++ Z_ +๐’ณโ”โ” ++ XX +๐’ฎ๐“‰๐’ถ๐’ท ++ X_ +๐’ตโ”โ” ++ XZ +``` +""" +function canonicalize_noncomm(t::Tableau) + for i in eachindex(t) phases(t)[i] = 0x00 end + loc = zero(Tableau, 2*length(t), nqubits(t)) + x_index = 0 + z_index = length(loc) + for i in eachindex(t) + for j in eachindex(t) + if comm(t[i], t[j]) == 0x01 + for k in eachindex(t) + if k !=i && k != j + if comm(t[k], t[i]) == 0x01 + mul_left!(t, k, j, phases=Val(false)) + end + if comm(t[k], t[j]) == 0x01 + mul_left!(t, k, i, phases=Val(false)) + end + end + end + if !(t[i] in loc) + x_index+=1 + loc[x_index]= t[i] + end + if !(t[j] in loc) + loc[z_index]= t[j] + z_index-=1 + end + end + end + end + ind = 2*x_index+1 #format tableau for SubsystemCodeTableau + k = x_index + m = length(t)-2*k + for i in range(k, stop =1, step=-1) + loc[i+m]= loc[i] + end + i = m+1 + j = m+k + while i < j #ensure anticommutative pairs are lined up correctly + loc[i], loc[j], = loc[j], loc[i] + i+=1 + j-=1 + end + s_index = m+k+1 + for i in eachindex(t) + if !(t[i] in loc) + loc[s_index]= t[i] + s_index+=1 + end + end + r = s_index-m-k-1 + if r <= nqubits(t) + d = destabilizerview(Destabilizer(Stabilizer(loc[m+k+1:s_index-1]))) + for i in eachindex(d) + loc[i] =d[i] + end + else for i in 1:m zero!(loc, i) end end + return SubsystemCodeTableau(loc, ind, r, m, k) +end + +""" +For a not-necessarily commutative set of Paulis S, +computed S', the [non-commutative canonical form](@ref canonicalize_noncomm) of of S. +For each pair Aโ‚–, Bโ‚– of anticommutative Paulis in S', add a qubit to each Pauli in the set: +X to Aโ‚–, Z to Bโ‚–, and I to each other operator to produce S'', a fully commutative set. Return +S'' as well as a list of the indices of the added qubits. + +The returned object is a Stabilizer that is also useful for the [`matroid_parent`](@ref) function. + +```jldoctest +julia> commutify(T"XX XZ XY")[1] ++ XXX ++ X__ ++ XZZ + +julia> commutify(T"XX XZ XY")[2] +3:3 +``` +""" +function commutify(t) + loc = canonicalize_noncomm(t) + commutative = zero(Stabilizer, 2*loc.k+loc.r, nqubits(loc)+loc.k) + puttableau!(commutative, logicalxview(loc), 0,0) + puttableau!(commutative, stabilizerview(loc), loc.k,0) + puttableau!(commutative, logicalzview(loc), loc.k+loc.r,0) + x= T"X" + z=T"Z" + for i in 0:(loc.k-1) + puttableau!(commutative, x, i, nqubits(loc)+i) + puttableau!(commutative, z, i+loc.r+loc.k, nqubits(loc)+i) + end + to_delete = nqubits(loc)+1:nqubits(loc)+loc.k + return commutative, to_delete +end + +""" +For a given set S of Paulis that does not necessarily represent a state, +return a set of Paulis S' that represents a state. +S' is a superset of [commutified](@ref commutify) S. +Additionally returns two arrays representing deletions needed to produce S. +Based on [goodenough2024bipartiteentanglementnoisystabilizer](@cite) + +By deleting the qubits in the first output array from S', taking the [`normalizer`](@ref) of S', then +deleting the qubits in the second returned array from the [`normalizer`](@ref) of S', S is reproduced. + +```jldoctest +julia> matroid_parent(T"XX")[1] ++ X_X ++ XX_ ++ ZZZ + +julia> matroid_parent(T"XX")[2] +3:3 + +julia> matroid_parent(T"XX")[3] +3:2 +``` +""" +function matroid_parent(t::Tableau) + com, d1= commutify(t) + norm = normalizer(com.tab) + state, d2 = commutify(norm) + return state, d2, d1 +end + """ Return the full Pauli group of a given length. Phases are ignored by default, but can be included by setting `phases=true`. @@ -127,15 +385,21 @@ julia> normalizer(T"X") + X ``` """ -function normalizer(t::Tableau) +function normalizer(t::Tableau; phases=false) # For each `PauliOperator` p in the with same number of qubits as the `Stabilizer` s, iterate through s and check each # operator's commutivity with p. If they all commute, add p a vector of `PauliOperators`. Return the vector # converted to `Tableau`. n = nqubits(t) - pgroup = pauligroup(n, phases=false) - ptype = typeof(t[1]) - normalizer = ptype[] - for p in pgroup + ptype =typeof(P"I") + norm = ptype[] + + p = zero(PauliOperator, n) + paulis = ((false, false), (true, false), (false, true), (true, true)) + for i in Iterators.product(Iterators.repeated(paulis, n)...) + zero!(p) + for (j, k) in enumerate(i) + p[j] = k + end commutes = true for q in t if comm(p, q) == 0x01 @@ -143,11 +407,15 @@ function normalizer(t::Tableau) end end if commutes - push!(normalizer, p) + push!(norm, copy(p)) + end + if phases + for phase in [-1, 1im, -1im] + push!(norm, phase *p) + end end end - - return Tableau(normalizer) + return Tableau(norm) end """ @@ -181,7 +449,7 @@ end """ Return the subset of Paulis in a Stabilizer that have identity operators on all qubits corresponding to -the given subset, without the entries corresponding to subset. +the given subset, without the entries corresponding to subset. Based on [goodenough2024bipartiteentanglementnoisystabilizer](@cite) ```jldoctest julia> contractor(S"_X X_", [1]) diff --git a/src/mul_leftright.jl b/src/mul_leftright.jl index 9803ed2b5..08f905664 100644 --- a/src/mul_leftright.jl +++ b/src/mul_leftright.jl @@ -157,6 +157,12 @@ end # On Tableaux ############################## +@inline function mul_left!(s::Tableau, m, t::Tableau, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_left!((@view s.xzs[:,m]), (@view t.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) + s +end + @inline function mul_left!(s::Tableau, m, i; phases::Val{B}=Val(true)) where B extra_phase = mul_left!((@view s.xzs[:,m]), (@view s.xzs[:,i]); phases=phases) B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) diff --git a/src/project_trace_reset.jl b/src/project_trace_reset.jl index 4b2e6e2e5..25aed09ce 100644 --- a/src/project_trace_reset.jl +++ b/src/project_trace_reset.jl @@ -885,5 +885,6 @@ julia> delete_columns(S"XYZ YZX ZXY", [1,3]) See also: [`traceout!`](@ref) """ function delete_columns(๐’ฎ::Stabilizer, subset) + if length(๐’ฎ) == 0 return ๐’ฎ end return ๐’ฎ[:, setdiff(1:nqubits(๐’ฎ), subset)] end diff --git a/test/test_group_tableaux.jl b/test/test_group_tableaux.jl index d208afdff..fb3240441 100644 --- a/test/test_group_tableaux.jl +++ b/test/test_group_tableaux.jl @@ -1,4 +1,4 @@ -@testitem "group theory routines" begin +@testitem "group theory tools" begin using Test using Random using QuantumClifford @@ -8,41 +8,82 @@ # Zero function(in groupify) slows down around 2^30(n=30),eventually breaks small_test_sizes = [1, 2, 3, 4, 5, 7] # Pauligroup slows around n = 8 - @testset "group_tableaux" begin + @testset "group theory tools" begin #Test groupify for n in [1, test_sizes...] s = random_stabilizer(n) s_test = copy(s) group = groupify(s) @test length(group) == 2^n - unchanged = true for stabilizer in group apply!(s, stabilizer) - if !(s == s_test) - unchanged = false - end - @test unchanged == true + @test s == s_test end end #Test minimal_generating_set - for n in [1, small_test_sizes...] + for n in [1, test_sizes...] s = random_stabilizer(n) group = groupify(s) gen_set = minimal_generating_set(Stabilizer(group)) + @test length(group) == 2^(length(gen_set)) new_group = groupify(gen_set) - canonicalize!(Stabilizer(group)) - canonicalize!(Stabilizer(new_group)) - @test group == new_group - s = zero(Stabilizer, rand(1:(2*n)), n) - for i in 1:length(s) - s[i] = random_pauli(n) + s1, _, r = canonicalize!(Stabilizer(group), ranks = true) + s2, _, r = canonicalize!(Stabilizer(new_group), ranks=true) + @test group[1:r, :] == new_group[1:r, :] + end + #Test canonicalize_noncomm + for n in [1, small_test_sizes...] + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + loc = canonicalize_noncomm(t) + for i in 1:loc.k + for j in 1:loc.k + if i == j + @test comm(logicalxview(loc)[i], logicalzview(loc)[j]) == 0x01 + else @test comm(logicalxview(loc)[i], logicalzview(loc)[j]) == 0x00 end + end end - gen_set = minimal_generating_set(s) - new_group = groupify(s) - for operator in s - @test operator in new_group + for i in stabilizerview(loc) + for j in stabilizerview(loc) @test comm(i, j) == 0x00 end + for j in logicalzview(loc) @test comm(i, j) == 0x00 end + for j in logicalxview(loc) @test comm(i, j) == 0x00 end end end + #Test commutify + for n in [1, small_test_sizes...] + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + c, d = commutify(t) + for i in c + for j in c + @test comm(i, j) == 0x00 + end + end + for i in d + for p in c + @test p[i] != (true, true) + end + end + loc1= delete_columns(c, d) + loc2 = canonicalize_noncomm(t).tab + for i in eachindex(delete_columns(c, d)) + end + end + #Test matroid_parent + for n in [1,2,3,4,5] + t = zero(QuantumClifford.Tableau, 2*n, n) + for i in eachindex(t) t[i] = random_pauli(n) end + e, d2, d1 = matroid_parent(t) + s = Stabilizer(groupify(e)) + for i in e for j in e @test comm(i, j)==0x00 end end + @test 2^(nqubits(s)) == length(s) #assumes commutativise works + #find original tableau from matroid_parentded state, ignoring phases + inverted = delete_columns(Stabilizer(normalizer(delete_columns(Stabilizer(e), d2).tab)), d1) + original = Stabilizer(groupify(Stabilizer(t))) + canonicalize!(inverted) + canonicalize!(original) + @test inverted[1:length(inverted)].tab.xzs == original[1:length(inverted)].tab.xzs + end #Test pauligroup for n in [1, small_test_sizes...] @test length(QuantumClifford.pauligroup(n, phases=false)) == 4^n