diff --git a/CHANGELOG.md b/CHANGELOG.md index f69abf7b0..3d3dabca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ # News +## v0.8.17 - dev +- **(fix)** Some `affectedqubits` methods were returning single integers instead of a one-tuple. +- The non-public `ECC` module has seen a few improvements (a `naive_encoding_circuit` implementation and a few new codes), as well as some breaking changes to API. + ## v0.8.16 - 2023-08-31 - **(breaking)** Changes to the circuit generation in the non-public ECC module. Adding a Shor syndrome measurement circuit. diff --git a/Project.toml b/Project.toml index 3ae3fc876..3ee2bd84d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov "] -version = "0.8.16" +version = "0.8.17" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/docs/make.jl b/docs/make.jl index b706a6153..48920028c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,13 +15,13 @@ ENV["COLUMNS"] = 80 bib = CitationBibliography(joinpath(@__DIR__,"src/references.bib"),style=:authoryear) makedocs( -bib, +plugins = [bib], doctest = false, clean = true, sitename = "QuantumClifford.jl", -format = Documenter.HTML(), +format = Documenter.HTML(size_threshold_ignore = ["API.md"]), modules = [QuantumClifford, QuantumClifford.Experimental.NoisyCircuits, QuantumInterface], -strict = Documenter.except(:missing_docs), +warnonly = [:missing_docs], authors = "Stefan Krastanov", pages = [ "QuantumClifford.jl" => "index.md", diff --git a/docs/src/ecc_example_sim.md b/docs/src/ecc_example_sim.md index e20f66703..5ee627ca8 100644 --- a/docs/src/ecc_example_sim.md +++ b/docs/src/ecc_example_sim.md @@ -12,7 +12,7 @@ Consider Steane 7-qubit code: ```@example 1 using QuantumClifford -using QuantumClifford.ECC: Steane7, naive_syndrome_circuit, encoding_circuit, parity_checks, code_s, code_n +using QuantumClifford.ECC: Steane7, naive_syndrome_circuit, naive_encoding_circuit, parity_checks, code_s, code_n using Quantikz code = Steane7() @@ -21,7 +21,7 @@ H = parity_checks(code) ... and the corresponding encoding circuit ```@example 1 -ecirc = encoding_circuit(code) +ecirc = naive_encoding_circuit(code) ``` ... and the corresponding syndrome measurement circuit (the non-fault tolerant one) diff --git a/docs/src/references.bib b/docs/src/references.bib index d522b3ffc..d260be4d7 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -158,12 +158,44 @@ @article{gullans2020dynamical publisher={APS} } -@misc{hein2006entanglement, - title = {Entanglement in Graph States and its Applications}, - url = {http://arxiv.org/abs/quant-ph/0602096}, - journaltitle = {arXiv preprint arXiv:quant-ph/0602096}, - author = {Hein, M. and Dür, W. and Eisert, J. and Raussendorf, R. and Nest, M. Van den and Briegel, H.-J.}, - year = {2006}, +@article{hein2006entanglement, + title={Entanglement in graph states and its applications}, + author={Hein, Marc and D{\"u}r, Wolfgang and Eisert, Jens and Raussendorf, Robert and Nest, M and Briegel, H-J}, + journal={arXiv preprint quant-ph/0602096}, + url={https://arxiv.org/abs/quant-ph/0602096}, + doi={10.48550/arXiv.quant-ph/0602096}, + year={2006} +} + +% Encoding circuits + +@article{cleve1997efficient, + title={Efficient computations of encodings for quantum error correction}, + author={Cleve, Richard and Gottesman, Daniel}, + journal={Physical Review A}, + volume={56}, + number={1}, + pages={76}, + year={1997}, + publisher={APS} +} + +@inproceedings{grassl2011variations, + title={Variations on encoding circuits for stabilizer quantum codes}, + author={Grassl, Markus}, + booktitle={International Conference on Coding and Cryptology}, + pages={142--158}, + year={2011}, + organization={Springer} +} + +@article{grassl2002algorithmic, + title={Algorithmic aspects of quantum error-correcting codes}, + author={Grassl, Markus}, + journal={Mathematics of Quantum Computation}, + pages={223--252}, + year={2002}, + publisher={CRC Press Boca Raton, FL} } % Examples of results that employ the tableaux formalism diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6c77b1106..d4ac9ba26 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -50,6 +50,7 @@ export sHadamard, sPhase, sInvPhase, SingleQubitOperator, sId1, sX, sY, sZ, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, + sZCrY, # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, @@ -504,6 +505,11 @@ operators are tracked as well. When the constructor is called on an incomplete [`Stabilizer`](@ref) it automatically calculates the destabilizers and logical operators, following chapter 4 of [gottesman1997stabilizer](@cite). +Under the hood the conversion uses the [`canonicalize_gott!`](@ref) canonicalization. +That canonicalization permutes the columns of the tableau, but we automatically undo the +column permutation in the preparation of a `MixedDestabilizer` so that qubits are not reindexed. +The boolean keyword arguments `undoperm` and `reportperm` can be used to control this behavior +and to report the permutations explicitly. See also: [`stabilizerview`](@ref), [`destabilizerview`](@ref), [`logicalxview`](@ref), [`logicalzview`](@ref) """ @@ -513,7 +519,7 @@ mutable struct MixedDestabilizer{T<:Tableau} <: AbstractStabilizer end # Added a lot of type assertions to help Julia infer types -function MixedDestabilizer(stab::Stabilizer{T}; undoperm=true) where {T} +function MixedDestabilizer(stab::Stabilizer{T}; undoperm=true, reportperm=false) where {T} rows,n = size(stab) stab, r, s, permx, permz = canonicalize_gott!(copy(stab)) t = zero(T, n*2, n) @@ -550,8 +556,13 @@ function MixedDestabilizer(stab::Stabilizer{T}; undoperm=true) where {T} end if undoperm t = t[:,invperm(permx[permz])]::T + return MixedDestabilizer(t, r+s)::MixedDestabilizer{T} + end + if reportperm + return (MixedDestabilizer(t, r+s)::MixedDestabilizer{T}, r, permx, permz) + else + return MixedDestabilizer(t, r+s)::MixedDestabilizer{T} end - MixedDestabilizer(t, r+s)::MixedDestabilizer{T} end function MixedDestabilizer(d::Destabilizer, r::Int) diff --git a/src/affectedqubits.jl b/src/affectedqubits.jl index c5ff21299..09c9217e6 100644 --- a/src/affectedqubits.jl +++ b/src/affectedqubits.jl @@ -1,8 +1,8 @@ """A method giving the qubits acted upon by a given operation. Part of the Noise interface.""" function affectedqubits end -affectedqubits(g::AbstractSingleQubitOperator) = (g.q) +affectedqubits(g::AbstractSingleQubitOperator) = (g.q,) affectedqubits(g::AbstractTwoQubitOperator) = (g.q1, g.q2) -affectedqubits(g::NoisyGate) = affectedqubits(g.gate) +affectedqubits(g::NoisyGate) = affectedqubits(g.gate,) affectedqubits(g::SparseGate) = g.indices affectedqubits(b::BellMeasurement) = map(m->m.qubit, b.measurements) affectedqubits(r::Reset) = r.indices diff --git a/src/canonicalization.jl b/src/canonicalization.jl index 49b8cc729..907121744 100644 --- a/src/canonicalization.jl +++ b/src/canonicalization.jl @@ -258,5 +258,6 @@ function _canonicalize_gott!(stabilizer::Stabilizer; phases::Val{B}=Val(true)) w end zperm, s = gott_standard_form_indices((@view xzs[end÷2+1:end,:]),rows,columns,skip=r) permute!(stabilizer,zperm) + # we have r+s==rows (or we have trailing rows that are zeroed) stabilizer, r, s, xperm, zperm end diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 0410b5a60..b83f4e793 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -1,190 +1,48 @@ module ECC +using LinearAlgebra using QuantumClifford -using QuantumClifford: AbstractOperation +using QuantumClifford: AbstractOperation, AbstractStabilizer import QuantumClifford: Stabilizer, MixedDestabilizer +using DocStringExtensions +using Combinatorics: combinations abstract type AbstractECC end -"""The encoding circuit of a given code.""" -function encoding_circuit end +export Shor9, Steane7, Cleve8, Perfect5, Bitflip3, + parity_checks, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, + code_n, code_s, code_k, rate, distance, + isdegenerate, faults_matrix """Parity check tableau of a code.""" function parity_checks end +parity_checks(s::Stabilizer) = s Stabilizer(c::AbstractECC) = parity_checks(c) -MixedDestabilizer(c::AbstractECC) = MixedDestabilizer(Stabilizer(c)) +MixedDestabilizer(c::AbstractECC; kwarg...) = MixedDestabilizer(Stabilizer(c); kwarg...) """The number of physical qubits in a code.""" function code_n end +code_n(c::AbstractECC) = code_n(parity_checks(c)) + +code_n(s::Stabilizer) = nqubits(s) + """The number of stabilizer checks in a code.""" -function code_s(c::AbstractECC) - s = length(parity_checks(c)) - return s -end +function code_s end + +code_s(c::AbstractECC) = code_s(parity_checks(c)) +code_s(s::Stabilizer) = length(s) """The number of logical qubits in a code.""" -function code_k(c::AbstractECC) - k = code_n(c) - code_s(c) - return k -end +code_k(c) = code_n(c) - code_s(c) """The rate of a code.""" -function rate(c::AbstractECC) - rate = code_k(c)//code_s(c) +function rate(c) + rate = code_k(c)//code_n(c) return rate end -"""Number of physical qubits for a given parity check tableau""" -function code_n(parity_check_tableau) - return size(parity_check_tableau)[2] -end - -"""Generate the non-fault-tolerant stabilizer measurement cicuit for a given code instance or parity check tableau. - -Use the `ancillary_index` and `bit_index` arguments to offset where the corresponding part the circuit starts. - -Returns the circuit, the number of ancillary qubits that were added, and a list of bit indices that will store the measurement results. - -See also: [`shor_syndrome_circuit`](@ref) -""" -function naive_syndrome_circuit end - -function naive_syndrome_circuit(code_type::AbstractECC, ancillary_index=1, bit_index=1) - naive_syndrome_circuit(parity_checks(code_type), ancillary_index, bit_index) -end - -"""Naive Pauli measurement circuit using a single ancillary qubit. - -Not a fault-tolerant circuit, but useful for testing purposes. - -Measures the corresponding `PauliOperator` by using conditional gates into an ancillary -qubit at index `nqubits(p)+ancillary_index` and stores the measurement result -into classical bit `bit_index`. - -See also: [`naive_syndrome_circuit`](@ref), [`shor_ancillary_paulimeasurement`](@ref)""" -function naive_ancillary_paulimeasurement(p::PauliOperator, ancillary_index=1, bit_index=1) - circuit = AbstractOperation[] - numQubits = nqubits(p) - for qubit in 1:numQubits - if p[qubit] == (1,0) - push!(circuit, sXCX(qubit, numQubits + ancillary_index)) - elseif p[qubit] == (0,1) - push!(circuit, sCNOT(qubit, numQubits + ancillary_index)) - elseif p[qubit] == (1,1) - push!(circuit, sYCX(qubit, numQubits + ancillary_index)) - end - end - mz = sMRZ(numQubits + ancillary_index, bit_index) - push!(circuit, mz) - - return circuit -end - -function naive_syndrome_circuit(parity_check_tableau, ancillary_index=1, bit_index=1) - naive_sc = AbstractOperation[] - ancillaries = 0 - bits = 0 - for check in parity_check_tableau - append!(naive_sc,naive_ancillary_paulimeasurement(check, ancillary_index+ancillaries, bit_index+bits)) - ancillaries +=1 - bits +=1 - end - - return naive_sc, ancillaries, bit_index:bit_index+bits-1 -end - -"""Generate the Shor fault-tolerant stabilizer measurement cicuit for a given code instance or parity check tableau. - -Use the `ancillary_index` and `bit_index` arguments to offset where the corresponding part the circuit starts. -Ancillary qubits - -Returns: - - The cat state preparation circuit. - - The Shor syndrome measurement circuit. - - The number of ancillary qubits that were added. - - The list of bit indices that store the final measurement results. - -See also: [`naive_syndrome_circuit`](@ref) -""" -function shor_syndrome_circuit end - -function shor_syndrome_circuit(code_type::AbstractECC, ancillary_index=1, bit_index=1) - shor_syndrome_circuit(parity_checks(code_type), ancillary_index, bit_index) -end - -"""Shor's Pauli measurement circuit using a multiple entangled ancillary qubits. - -A fault-tolerant circuit. - -Measures the corresponding PauliOperator by using conditional gates into multiple ancillary -entangled qubits starting at index `nqubits(p)+ancillary_index` -and stores the measurement result into classical bits starting at `bit_index`. -The final measurement result is the XOR of all the bits. - -Returns: - - The cat state preparation circuit. - - The Shor syndrome measurement circuit. - - One more than the index of the last added ancillary qubit. - - One more than the index of the last added classical bit. - -See also: [`naive_syndrome_circuit`](@ref), [`naive_ancillary_paulimeasurement`](@ref)""" -function shor_ancillary_paulimeasurement(p::PauliOperator, ancillary_index=1, bit_index=1) - init_ancil_index = ancillary_index - circuit = AbstractOperation[] - measurements = AbstractOperation[] - numQubits = nqubits(p) - for qubit in 1:numQubits - if p[qubit] == (1,0) - push!(circuit, sXCZ(qubit, numQubits + ancillary_index)) - elseif p[qubit] == (0,1) - push!(circuit, sZCZ(qubit, numQubits + ancillary_index)) - elseif p[qubit] == (1,1) - push!(circuit, sYCZ(qubit, numQubits + ancillary_index)) - end - if p[qubit] != (0,0) - push!(measurements, sMRX(numQubits + ancillary_index, bit_index)) - ancillary_index +=1 - bit_index +=1 - end - end - circuit = vcat(circuit,measurements) - - cat_state_circuit = AbstractOperation[] - push!(cat_state_circuit, sHadamard(numQubits + init_ancil_index)) - for i in (init_ancil_index+1):(ancillary_index -1) - push!(cat_state_circuit, sCNOT(numQubits + (i - 1), numQubits + i)) - end - - return cat_state_circuit, circuit, ancillary_index, bit_index -end - -function shor_syndrome_circuit(parity_check_tableau, ancillary_index=1, bit_index=1) - shor_sc = AbstractOperation[] - xor_indices = [] - cat_circuit = AbstractOperation[] - initial_ancillary_index = ancillary_index - - for check in parity_check_tableau - cat_circ, circ, new_ancillary_index, new_bit_index = shor_ancillary_paulimeasurement(check, ancillary_index, bit_index) - push!(xor_indices, collect(bit_index:new_bit_index-1)) - - append!(shor_sc, circ) - append!(cat_circuit, cat_circ) - - ancillary_index = new_ancillary_index - bit_index = new_bit_index - end - - final_bits = 0 - for indices in xor_indices - push!(shor_sc, QuantumClifford.ClassicalXOR(indices, bit_index+final_bits)) - final_bits += 1 - end - - return cat_circuit, shor_sc, ancillary_index-initial_ancillary_index, bit_index:bit_index+final_bits-1 -end """The distance of a code.""" function distance end @@ -196,13 +54,13 @@ function parity_matrix(c::AbstractECC) end """Logical X operations of a code.""" -function logx_ops(c::AbstractECC) +function logx_ops(c) md = MixedDestabilizer(parity_checks(c)) logicalxview(md) end """Logical Z operations of a code.""" -function logz_ops(c::AbstractECC) +function logz_ops(c) md = MixedDestabilizer(parity_checks(c)) logicalzview(md) end @@ -392,10 +250,50 @@ function faults_matrix(c::AbstractECC) return faults_matrix(parity_checks(c)) end -# TODO implement isdegenerate +""" +$TYPEDSIGNATURES + +Check if the code is degenerate with respect to a given set of error or with respect to all +"up to d physical-qubit" errors (defaulting to d=1). + +```jldoctest +julia> using QuantumClifford.ECC + +julia> isdegenerate(Shor9(), [single_z(9,1), single_z(9,2)]) +true + +julia> isdegenerate(Shor9(), [single_z(9,1), single_x(9,1)]) +false + +julia> isdegenerate(Steane7(), 1) +false + +julia> isdegenerate(Steane7(), 2) +true +``` +""" +function isdegenerate end + +isdegenerate(c::AbstractECC, args...) = isdegenerate(parity_checks(c), args...) +isdegenerate(c::AbstractStabilizer, args...) = isdegenerate(stabilizerview(c), args...) + +function isdegenerate(H::Stabilizer, errors) # Described in https://quantumcomputing.stackexchange.com/questions/27279 + syndromes = comm.((H,), errors) # TODO This can be optimized by having something that always returns bitvectors + return length(Set(syndromes)) != length(errors) +end + +function isdegenerate(H::Stabilizer, d::Int=1) + n = nqubits(H) + errors = [begin p=zero(PauliOperator,n); for i in bits; p[i]=op; end; p end for bits in combinations(1:n, d) for op in ((true,false), (false,true))] + return isdegenerate(H, errors) +end + +include("circuits.jl") -include("./bitflipcode.jl") -include("./shorcode.jl") -include("./steanecode.jl") +include("codes/bitflipcode.jl") +include("codes/fivequbit.jl") +include("codes/steanecode.jl") +include("codes/shorcode.jl") +include("codes/clevecode.jl") end #module diff --git a/src/ecc/circuits.jl b/src/ecc/circuits.jl new file mode 100644 index 000000000..0f62a7057 --- /dev/null +++ b/src/ecc/circuits.jl @@ -0,0 +1,228 @@ +"""Encoding physical qubits into a larger logical code. + +The initial physical qubits to be encoded have to be at indices `n-k+1:n`. + +!!! info "Encoding circuits are not fault-tolerant" + Encoding circuits are not fault-tolerant, and thus should not be used in practice. + Instead, you should measure the stabilizers of the code and the logical observables, + thus projecting into the code space (which can be fault-tolerant). + +The canonicalization operation performed on the code may permute the qubits (see [canonicalize_gott!](@ref)). +That permutation is corrected for with SWAP gates by default (controlled by the `undoperm` keyword argument). + +Based on [gottesman1997stabilizer](@cite) and [cleve1997efficient](@cite), +however it seems the published algorithm has some errors. +Consult the erratum, as well as the more recent [grassl2002algorithmic](@cite) and [grassl2011variations](@cite), +and be aware that this implementation also uses H instead of Z gates. +""" +function naive_encoding_circuit(code; undoperm=true) + n = code_n(code) + k = code_k(code) + md, r, permx, permz = MixedDestabilizer(code, undoperm=false, reportperm=true); + circ = QuantumClifford.AbstractOperation[] + X = logicalxview(md) + Z = logicalzview(md) + S = stabilizerview(md) + for i in 1:k + for t in 1:n-k + if X[i,t][1] == true + push!(circ, sCNOT(n-k+i, t)) + end + end + end + for i in 1:r + push!(circ, sHadamard(i)) + if S[i,i][2] == true + push!(circ, sPhase(i)) + end + for t in 1:n + if i!=t + xz = S[i,t] + g = if xz == (true, true) # Y + sZCY + elseif xz == (true, false) # X + sZCX + elseif xz == (false, true) && !(i canonicalize! + + # creating a valid state purely algebraically + algebraicₙ = MixedDestabilizer(code) + algebraicₙ.rank = nqubits(algebraicₙ) + algebraicₙ = stabilizerview(algebraicₙ) |> canonicalize! + + @test (encodedₙ == algebraicₙ) + + #println("$codeexpr, $(encodedₙ == algebraicₙ)") + end +end diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index 851f95157..fd89a2262 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -1,18 +1,20 @@ using Test using QuantumClifford using QuantumClifford: mul_left! -using QuantumClifford.ECC: AbstractECC, Steane7, Shor9, Bitflip3, naive_syndrome_circuit, encoding_circuit, shor_syndrome_circuit, code_n, code_s +using QuantumClifford.ECC: AbstractECC, Steane7, Shor9, Perfect5, Bitflip3, Cleve8, naive_syndrome_circuit, naive_encoding_circuit, shor_syndrome_circuit, code_n, code_s, parity_checks codes = [ Bitflip3(), Steane7(), Shor9(), + Perfect5(), + Cleve8() ] ## function pframe_naive_vs_shor_syndrome(code) - ecirc = encoding_circuit(code) + ecirc = naive_encoding_circuit(code) naive_scirc, naive_ancillaries = naive_syndrome_circuit(code) shor_cat_scirc, shor_scirc, shor_ancillaries, shor_bits = shor_syndrome_circuit(code) nframes = 10