From 3280b6b07fdeb210fb952951e6df442fcabd3c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= Date: Fri, 1 Nov 2024 13:27:20 +0100 Subject: [PATCH 1/7] add apply!(frame::PauliFrame, op::PauliMeasurement) abstraction --- src/pauli_frames.jl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/pauli_frames.jl b/src/pauli_frames.jl index 45e9763c8..304923789 100644 --- a/src/pauli_frames.jl +++ b/src/pauli_frames.jl @@ -26,7 +26,8 @@ $(TYPEDSIGNATURES) Prepare an empty set of Pauli frames with the given number of `frames` and `qubits`. Preallocates spaces for `measurement` number of measurements. """ function PauliFrame(frames, qubits, measurements) - stab = fastcolumn(zero(Stabilizer, frames, qubits)) # TODO this should really be a Tableau + # one extra qubit for ancilla measurements + stab = fastcolumn(zero(Stabilizer, frames, qubits + 1)) # TODO this should really be a Tableau bits = zeros(Bool, frames, measurements) frame = PauliFrame(stab, bits) initZ!(frame) @@ -120,6 +121,24 @@ function apply!(frame::PauliFrame, op::sMRZ) # TODO sMRY, and faster sMRX return frame end +function apply!(frame::PauliFrame, op::PauliMeasurement) + # this is inspired by ECC.naive_ancillary_paulimeasurement. Not sure if it's better to import and call that. + n = nqubits(op.pauli) + for qubit in 1:n + if op.pauli[qubit] == (1, 0) + apply!(frame, sXCZ(qubit, n + 1)) + elseif op.pauli[qubit] == (0, 1) + apply!(frame, sCNOT(qubit, n + 1)) + elseif op.pauli[qubit] == (1, 1) + apply!(frame, sYCX(qubit, n + 1)) + end + end + op.pauli.phase[] == 0 || apply!(frame, sX(n + 1)) + apply!(frame, sMRX(n + 1, op.bit)) + + return frame +end + function applynoise!(frame::PauliFrame,noise::UnbiasedUncorrelatedNoise,i::Int) p = noise.p T = eltype(frame.frame.tab.xzs) From f8ca3668a59d84a8c96af672c05bd822e31bdf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= Date: Fri, 1 Nov 2024 14:33:41 +0100 Subject: [PATCH 2/7] add concrete_typeparams for PauliMeasurement --- src/sumtypes.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sumtypes.jl b/src/sumtypes.jl index 9a2cc4462..f496532b0 100644 --- a/src/sumtypes.jl +++ b/src/sumtypes.jl @@ -223,6 +223,12 @@ function concrete_typeparams(t::Type{NoiseOp}) ] end +function concrete_typeparams(::Type{PauliMeasurement}) + return [ + (Array{UInt8,0}, Vector{UInt64}), + ] +end + # XXX This has to happen after defining all the `concrete_typeparams` methods From 6eefe5a5b03849c2066df2b1fd205e3607c7f270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= Date: Fri, 1 Nov 2024 17:59:26 +0100 Subject: [PATCH 3/7] fix typo in apply!(frame::PauliFrame, op::PauliMeasurement) and correct nqubits(f::PauliFrame) --- src/pauli_frames.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pauli_frames.jl b/src/pauli_frames.jl index 304923789..9bde6f9ae 100644 --- a/src/pauli_frames.jl +++ b/src/pauli_frames.jl @@ -11,7 +11,7 @@ struct PauliFrame{T,S} <: AbstractQCState measurements::S # TODO check if when looping over this we are actually looping over the fast axis end -nqubits(f::PauliFrame) = nqubits(f.frame) +nqubits(f::PauliFrame) = nqubits(f.frame) - 1 # dont count the ancilla qubit Base.length(f::PauliFrame) = size(f.measurements, 1) Base.eachindex(f::PauliFrame) = 1:length(f) Base.copy(f::PauliFrame) = PauliFrame(copy(f.frame), copy(f.measurements)) @@ -122,7 +122,7 @@ function apply!(frame::PauliFrame, op::sMRZ) # TODO sMRY, and faster sMRX end function apply!(frame::PauliFrame, op::PauliMeasurement) - # this is inspired by ECC.naive_ancillary_paulimeasurement. Not sure if it's better to import and call that. + # this is inspired by ECC.naive_syndrome_circuit n = nqubits(op.pauli) for qubit in 1:n if op.pauli[qubit] == (1, 0) @@ -134,7 +134,7 @@ function apply!(frame::PauliFrame, op::PauliMeasurement) end end op.pauli.phase[] == 0 || apply!(frame, sX(n + 1)) - apply!(frame, sMRX(n + 1, op.bit)) + apply!(frame, sMRZ(n + 1, op.bit)) return frame end From 6c7cbbb1e27582c000daf2a1080baf53b81cb5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= Date: Fri, 1 Nov 2024 19:17:17 +0100 Subject: [PATCH 4/7] add PauliMeasurement in PauliFrame test --- test/test_pauliframe.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/test_pauliframe.jl b/test/test_pauliframe.jl index cc335934c..ece38ea30 100644 --- a/test/test_pauliframe.jl +++ b/test/test_pauliframe.jl @@ -88,4 +88,29 @@ @test all(0.25.*[1 0 0 0 1] .<= (sum(m, dims=1)[:,1:5])./n .<= 0.75.*[1 0 0 0 1]) end end + + @testset "PauliMeasurements" begin + n = 2000 + state = Register(one(MixedDestabilizer, 3), 5) + frame = PauliFrame(n, 3, 5) + + glassy_ghz_circuit = [ + sHadamard(1), sHadamard(2), sHadamard(3), + PauliMeasurement(P"ZZ_", 1), PauliMeasurement(P"_ZZ", 2), + sMZ(1, 3), sMZ(2, 4), sMZ(3, 5) + ] + for m in [pfmeasurements(pftrajectories(copy(frame), glassy_ghz_circuit)), + pfmeasurements(pftrajectories(glassy_ghz_circuit; trajectories=n, threads=false)), + pfmeasurements(pftrajectories(glassy_ghz_circuit; trajectories=n, threads=true))] + + # decode based on measurement outcomes + for r in eachrow(m) + r[4] ⊻= r[1] + r[5] ⊻= r[1] ⊻ r[2] + end + + # check that the correct correlations are present + @test all(m[:, 3] .== m[:, 4] .== m[:, 5]) + end + end end From 514ef6769bffc63da84be40d1c1ebc562f04406f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= Date: Fri, 1 Nov 2024 19:46:45 +0100 Subject: [PATCH 5/7] new tab interface for PauliFrame --- src/pauli_frames.jl | 2 ++ test/test_ecc_syndromes.jl | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pauli_frames.jl b/src/pauli_frames.jl index 9bde6f9ae..e3dc07f91 100644 --- a/src/pauli_frames.jl +++ b/src/pauli_frames.jl @@ -17,6 +17,8 @@ Base.eachindex(f::PauliFrame) = 1:length(f) Base.copy(f::PauliFrame) = PauliFrame(copy(f.frame), copy(f.measurements)) Base.view(frame::PauliFrame, r) = PauliFrame(view(frame.frame, r), view(frame.measurements, r, :)) +tab(f::PauliFrame) = Tableau(f.frame.tab.phases, nqubits(f), f.frame.tab.xzs) + fastrow(s::PauliFrame) = PauliFrame(fastrow(s.frame), s.measurements) fastcolumn(s::PauliFrame) = PauliFrame(fastcolumn(s.frame), s.measurements) diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index 0f6f67ea0..4090dbc67 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -32,8 +32,8 @@ p = random_pauli(dataqubits, realphase=true) pₙ = embed(naive_qubits, 1:dataqubits, p) pₛ = embed(shor_qubits, 1:dataqubits, p) - mul_left!(naive_frames.frame, pₙ) - mul_left!(shor_frames.frame, pₛ) + mul_left!(tab(naive_frames), pₙ) + mul_left!(tab(shor_frames), pₛ) # run the syndrome circuits using the public API pftrajectories(naive_frames, naive_scirc) pftrajectories(shor_frames, shor_scirc) From 7fea5fd568fd4532cbf20c4d4c6dc8aea9046cd6 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 21 Dec 2024 02:36:14 -0500 Subject: [PATCH 6/7] tests! more tests!!! --- CHANGELOG.md | 8 ++++++-- test/test_bitpack.jl | 34 +++++++++++++++++++++++++++++++++- test/test_ecc_base.jl | 3 +++ test/test_ecc_syndromes.jl | 31 ++++++++++++++++++++++++++----- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5077104..2c964959a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.14 - 2024-12-22 + +- `pftrajectories` now supports fast multiqubit measurements with `PauliMeasurement` in addition to the already supported single qubit measurements `sMX/Z/Y` and workarounds like `naive_syndrome_circuit`. + ## v0.9.13 - 2024-10-30 - New error-correction group theory tools: @@ -69,7 +73,7 @@ - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. - Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits. - Bump `QuantumOpticsBase.jl` package extension compat bound. -- **(fix)** Remove printing of spurious debug info from the PyBP decoder. +- **(fix)** Remove printing of spurious debug info from the PyBP decoder. - **(fix)** Failed compactification of gates now only raises a warning instead of throwing an error. Defaults to slower non-compactified gates. ## v0.9.3 - 2024-04-10 @@ -87,7 +91,7 @@ - Implemented `iscss` function to identify whether a given code is known to be a CSS (Calderbank-Shor-Steane) code. - Added the classical Reed-Muller code in the ECC module. - Added the surface code to the ECC module. - + ## v0.9.0 - 2024-03-19 - **(breaking)** The defaults in `random_pauli` are now `realphase=true` and `nophase=true`. diff --git a/test/test_bitpack.jl b/test/test_bitpack.jl index e5aae358c..be7ad212f 100644 --- a/test/test_bitpack.jl +++ b/test/test_bitpack.jl @@ -1,6 +1,6 @@ @testitem "Alternative bit packing" tags=[:bitpack] begin using Random - using QuantumClifford: Tableau + using QuantumClifford: Tableau, mul_left! @testset "alternative bit packing" begin for n in [1,3] # can not go higher than 4 (limitation from SIMD acting on transposed/strided arrays) @@ -20,6 +20,8 @@ after_cliff = stab_to_gf2(_after_clif); after_cliff_phases = phases(_after_clif); + after_mul = stab_to_gf2(mul_left!(copy(s64), p64)) + for int in [UInt8, UInt16, UInt32, UInt64] p = PauliOperator(p64.phase, N, collect(reinterpret(int,p64.xz))); xzs = collect(reinterpret(int, collect(xzs64))); @@ -45,8 +47,38 @@ @test after_cliff == stab_to_gf2(after_clifford) @test after_cliff_phases == phases(after_clifford) end + + @test after_mul == stab_to_gf2(mul_left!(copy(s), p)) end end end end + + @testset "fast column and fast row mul_left correctness" begin + reinterpret_stab(s) = Stabilizer(Tableau(copy(phases(s)), nqubits(s), collect(reinterpret(UInt8, collect(s.tab.xzs))))) + reinterpret_p(p) = PauliOperator(p.phase, nqubits(p), collect(reinterpret(UInt8, p.xz))) + for N in [7,8,9,33,61,62,63,64,65,66] + + s0 = random_stabilizer(2,N) + p = random_pauli(N) + s = copy(s0) + sr = QuantumClifford.fastrow(copy(s)) + sc = QuantumClifford.fastcolumn(copy(s)) + + mul_left!(s, p) + mul_left!(sr, p) + mul_left!(sc, p) + + s8 = reinterpret_stab(copy(s0)) + s8r = QuantumClifford.fastrow(copy(s8)) + s8c = QuantumClifford.fastcolumn(copy(s8)) + p8 = reinterpret_p(copy(p)) + mul_left!(s8, p8) + mul_left!(s8r, p8) + mul_left!(s8c, p8) + + @test stab_to_gf2(s) == stab_to_gf2(sr) == stab_to_gf2(sc) == stab_to_gf2(s8) == stab_to_gf2(s8r) == stab_to_gf2(s8c) + end + end + end end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 9f4a5ec9e..a374a7c31 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -69,11 +69,14 @@ const code_instance_args = Dict( function all_testablable_code_instances(;maxn=nothing) codeinstances = [] + i = 1 for t in subtypes(QuantumClifford.ECC.AbstractECC) for c in get(code_instance_args, t.name.name, []) codeinstance = t(c...) !isnothing(maxn) && nqubits(codeinstance) > maxn && continue push!(codeinstances, codeinstance) + #@show i, t, code_n(codeinstance), code_k(codeinstance), code_s(codeinstance), code_n(codeinstance)-code_k(codeinstance) + i += 1 end end return codeinstances diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index 4090dbc67..01fbc5469 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -5,6 +5,11 @@ include("test_ecc_base.jl") + using QuantumClifford: Tableau + reinterpret_frame(frame) = PauliFrame(reinterpret_stab(frame.frame), copy(frame.measurements)) + reinterpret_stab(s) = Stabilizer(Tableau(copy(phases(s)), nqubits(s), collect(reinterpret(UInt8, collect(s.tab.xzs)))[[1:1+(nqubits(s)-1)÷8;end÷2+1:end÷2+1+(nqubits(s)-1)÷8],:])) + reinterpret_p(p) = PauliOperator(p.phase, nqubits(p), collect(reinterpret(UInt8, p.xz))[[1:1+(nqubits(p)-1)÷8;end÷2+1:end÷2+1+(nqubits(p)-1)÷8]]) + function pframe_naive_vs_shor_syndrome(code) ecirc = naive_encoding_circuit(code) naive_scirc, naive_ancillaries = naive_syndrome_circuit(code) @@ -30,19 +35,35 @@ pftrajectories(shor_frames, vcat(ecirc, shor_cat_scirc)) # manually injecting the same type of noise in the frames -- not really a user accessible API p = random_pauli(dataqubits, realphase=true) - pₙ = embed(naive_qubits, 1:dataqubits, p) - pₛ = embed(shor_qubits, 1:dataqubits, p) - mul_left!(tab(naive_frames), pₙ) - mul_left!(tab(shor_frames), pₛ) + pₙ = embed(naive_qubits+1, 1:dataqubits, p) # +1 to account for the buffer qubit hidden in pauli frames + pₛ = embed(shor_qubits+1, 1:dataqubits, p) # +1 to account for the buffer qubit hidden in pauli frames + mul_left!(naive_frames.frame, pₙ) + mul_left!(shor_frames.frame, pₛ) # run the syndrome circuits using the public API pftrajectories(naive_frames, naive_scirc) pftrajectories(shor_frames, shor_scirc) @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] + + # just for completeness, let's also try bitpacking in UInt8 instead of the default UInt + _naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) + _shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) + naive_uint8 = reinterpret_frame(_naive_frames) + shor_uint8 = reinterpret_frame(_shor_frames) + pftrajectories(naive_uint8, ecirc) + pftrajectories(shor_uint8, vcat(ecirc, shor_cat_scirc)) + p_uint8 = reinterpret_p(p) + pₙ_uint8 = embed(naive_qubits+1, 1:dataqubits, p_uint8) + pₛ_uint8 = embed(shor_qubits+1, 1:dataqubits, p_uint8) + mul_left!(naive_uint8.frame, pₙ_uint8) + mul_left!(shor_uint8.frame, pₛ_uint8) + pftrajectories(naive_uint8, naive_scirc) + pftrajectories(shor_uint8, shor_scirc) + @test pfmeasurements(shor_uint8)[:,shor_bits] == pfmeasurements(shor_frames)[:,shor_bits] == pfmeasurements(naive_frames) == pfmeasurements(naive_uint8) end end @testset "naive and shor measurement circuits" begin - for c in all_testablable_code_instances() + for (i,c) in enumerate(all_testablable_code_instances()) pframe_naive_vs_shor_syndrome(c) end end From 88f9f9c582417049fdc31818a9ec89702ec28576 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 21 Dec 2024 02:37:32 -0500 Subject: [PATCH 7/7] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 03e95c299..0b84b0ac0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.14" +version = "0.9.15" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"