Skip to content

Commit c6bc6ee

Browse files
J-C-QKrastanov
andauthored
Simulate circuits containing PauliMeasurements using PauliFrames (#412)
Co-authored-by: Stefan Krastanov <[email protected]> Co-authored-by: Stefan Krastanov <[email protected]>
1 parent 70c8991 commit c6bc6ee

File tree

8 files changed

+121
-9
lines changed

8 files changed

+121
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
# News
77

8+
## v0.9.15 - 2024-12-22
9+
10+
- `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`.
11+
812
## v0.9.14 - 2024-11-03
913

1014
- **(fix)** `affectedqubits()` on `sMX`, `sMY`, and `sMR*`
@@ -75,7 +79,7 @@
7579
- Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module.
7680
- Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits.
7781
- Bump `QuantumOpticsBase.jl` package extension compat bound.
78-
- **(fix)** Remove printing of spurious debug info from the PyBP decoder.
82+
- **(fix)** Remove printing of spurious debug info from the PyBP decoder.
7983
- **(fix)** Failed compactification of gates now only raises a warning instead of throwing an error. Defaults to slower non-compactified gates.
8084

8185
## v0.9.3 - 2024-04-10
@@ -93,7 +97,7 @@
9397
- Implemented `iscss` function to identify whether a given code is known to be a CSS (Calderbank-Shor-Steane) code.
9498
- Added the classical Reed-Muller code in the ECC module.
9599
- Added the surface code to the ECC module.
96-
100+
97101
## v0.9.0 - 2024-03-19
98102

99103
- **(breaking)** The defaults in `random_pauli` are now `realphase=true` and `nophase=true`.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "QuantumClifford"
22
uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1"
33
authors = ["Stefan Krastanov <[email protected]> and QuantumSavory community members"]
4-
version = "0.9.14"
4+
version = "0.9.15"
55

66
[deps]
77
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"

src/pauli_frames.jl

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ struct PauliFrame{T,S} <: AbstractQCState
1111
measurements::S # TODO check if when looping over this we are actually looping over the fast axis
1212
end
1313

14-
nqubits(f::PauliFrame) = nqubits(f.frame)
14+
nqubits(f::PauliFrame) = nqubits(f.frame) - 1 # dont count the ancilla qubit
1515
Base.length(f::PauliFrame) = size(f.measurements, 1)
1616
Base.eachindex(f::PauliFrame) = 1:length(f)
1717
Base.copy(f::PauliFrame) = PauliFrame(copy(f.frame), copy(f.measurements))
1818
Base.view(frame::PauliFrame, r) = PauliFrame(view(frame.frame, r), view(frame.measurements, r, :))
1919

20+
tab(f::PauliFrame) = Tableau(f.frame.tab.phases, nqubits(f), f.frame.tab.xzs)
21+
2022
fastrow(s::PauliFrame) = PauliFrame(fastrow(s.frame), s.measurements)
2123
fastcolumn(s::PauliFrame) = PauliFrame(fastcolumn(s.frame), s.measurements)
2224

@@ -26,7 +28,8 @@ $(TYPEDSIGNATURES)
2628
Prepare an empty set of Pauli frames with the given number of `frames` and `qubits`. Preallocates spaces for `measurement` number of measurements.
2729
"""
2830
function PauliFrame(frames, qubits, measurements)
29-
stab = fastcolumn(zero(Stabilizer, frames, qubits)) # TODO this should really be a Tableau
31+
# one extra qubit for ancilla measurements
32+
stab = fastcolumn(zero(Stabilizer, frames, qubits + 1)) # TODO this should really be a Tableau
3033
bits = zeros(Bool, frames, measurements)
3134
frame = PauliFrame(stab, bits)
3235
initZ!(frame)
@@ -112,6 +115,24 @@ function apply!(frame::PauliFrame, op::sMRZ) # TODO sMRY, and faster sMRX
112115
return frame
113116
end
114117

118+
function apply!(frame::PauliFrame, op::PauliMeasurement)
119+
# this is inspired by ECC.naive_syndrome_circuit
120+
n = nqubits(op.pauli)
121+
for qubit in 1:n
122+
if op.pauli[qubit] == (1, 0)
123+
apply!(frame, sXCZ(qubit, n + 1))
124+
elseif op.pauli[qubit] == (0, 1)
125+
apply!(frame, sCNOT(qubit, n + 1))
126+
elseif op.pauli[qubit] == (1, 1)
127+
apply!(frame, sYCX(qubit, n + 1))
128+
end
129+
end
130+
op.pauli.phase[] == 0 || apply!(frame, sX(n + 1))
131+
apply!(frame, sMRZ(n + 1, op.bit))
132+
133+
return frame
134+
end
135+
115136
function applynoise!(frame::PauliFrame,noise::UnbiasedUncorrelatedNoise,i::Int)
116137
p = noise.p
117138
xzs = tab(frame.frame).xzs

src/sumtypes.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ function concrete_typeparams(t::Type{NoiseOp})
223223
]
224224
end
225225

226+
function concrete_typeparams(::Type{PauliMeasurement})
227+
return [
228+
(Array{UInt8,0}, Vector{UInt64}),
229+
]
230+
end
231+
226232

227233
# XXX This has to happen after defining all the `concrete_typeparams` methods
228234

test/test_bitpack.jl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@testitem "Alternative bit packing" tags=[:bitpack] begin
22
using Random
3-
using QuantumClifford: Tableau
3+
using QuantumClifford: Tableau, mul_left!
44

55
@testset "alternative bit packing" begin
66
for n in [1,3] # can not go higher than 4 (limitation from SIMD acting on transposed/strided arrays)
@@ -20,6 +20,8 @@
2020
after_cliff = stab_to_gf2(_after_clif);
2121
after_cliff_phases = phases(_after_clif);
2222

23+
after_mul = stab_to_gf2(mul_left!(copy(s64), p64))
24+
2325
for int in [UInt8, UInt16, UInt32, UInt64]
2426
p = PauliOperator(p64.phase, N, collect(reinterpret(int,p64.xz)));
2527
xzs = collect(reinterpret(int, collect(xzs64)));
@@ -45,8 +47,38 @@
4547
@test after_cliff == stab_to_gf2(after_clifford)
4648
@test after_cliff_phases == phases(after_clifford)
4749
end
50+
51+
@test after_mul == stab_to_gf2(mul_left!(copy(s), p))
4852
end
4953
end
5054
end
5155
end
56+
57+
@testset "fast column and fast row mul_left correctness" begin
58+
reinterpret_stab(s) = Stabilizer(Tableau(copy(phases(s)), nqubits(s), collect(reinterpret(UInt8, collect(s.tab.xzs)))))
59+
reinterpret_p(p) = PauliOperator(p.phase, nqubits(p), collect(reinterpret(UInt8, p.xz)))
60+
for N in [7,8,9,33,61,62,63,64,65,66]
61+
62+
s0 = random_stabilizer(2,N)
63+
p = random_pauli(N)
64+
s = copy(s0)
65+
sr = QuantumClifford.fastrow(copy(s))
66+
sc = QuantumClifford.fastcolumn(copy(s))
67+
68+
mul_left!(s, p)
69+
mul_left!(sr, p)
70+
mul_left!(sc, p)
71+
72+
s8 = reinterpret_stab(copy(s0))
73+
s8r = QuantumClifford.fastrow(copy(s8))
74+
s8c = QuantumClifford.fastcolumn(copy(s8))
75+
p8 = reinterpret_p(copy(p))
76+
mul_left!(s8, p8)
77+
mul_left!(s8r, p8)
78+
mul_left!(s8c, p8)
79+
80+
@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)
81+
end
82+
end
83+
end
5284
end

test/test_ecc_base.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,14 @@ const code_instance_args = Dict(
160160

161161
function all_testablable_code_instances(;maxn=nothing)
162162
codeinstances = []
163+
i = 1
163164
for t in subtypes(QuantumClifford.ECC.AbstractECC)
164165
for c in get(code_instance_args, t.name.name, [])
165166
codeinstance = t(c...)
166167
!isnothing(maxn) && nqubits(codeinstance) > maxn && continue
167168
push!(codeinstances, codeinstance)
169+
#@show i, t, code_n(codeinstance), code_k(codeinstance), code_s(codeinstance), code_n(codeinstance)-code_k(codeinstance)
170+
i += 1
168171
end
169172
end
170173
return codeinstances

test/test_ecc_syndromes.jl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
include("test_ecc_base.jl")
77

8+
using QuantumClifford: Tableau
9+
reinterpret_frame(frame) = PauliFrame(reinterpret_stab(frame.frame), copy(frame.measurements))
10+
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],:]))
11+
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]])
12+
813
function pframe_naive_vs_shor_syndrome(code)
914
ecirc = naive_encoding_circuit(code)
1015
naive_scirc, naive_ancillaries = naive_syndrome_circuit(code)
@@ -30,19 +35,35 @@
3035
pftrajectories(shor_frames, vcat(ecirc, shor_cat_scirc))
3136
# manually injecting the same type of noise in the frames -- not really a user accessible API
3237
p = random_pauli(dataqubits, realphase=true)
33-
pₙ = embed(naive_qubits, 1:dataqubits, p)
34-
pₛ = embed(shor_qubits, 1:dataqubits, p)
38+
pₙ = embed(naive_qubits+1, 1:dataqubits, p) # +1 to account for the buffer qubit hidden in pauli frames
39+
pₛ = embed(shor_qubits+1, 1:dataqubits, p) # +1 to account for the buffer qubit hidden in pauli frames
3540
mul_left!(naive_frames.frame, pₙ)
3641
mul_left!(shor_frames.frame, pₛ)
3742
# run the syndrome circuits using the public API
3843
pftrajectories(naive_frames, naive_scirc)
3944
pftrajectories(shor_frames, shor_scirc)
4045
@test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits]
46+
47+
# just for completeness, let's also try bitpacking in UInt8 instead of the default UInt
48+
_naive_frames = PauliFrame(nframes, naive_qubits, syndromebits)
49+
_shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits))
50+
naive_uint8 = reinterpret_frame(_naive_frames)
51+
shor_uint8 = reinterpret_frame(_shor_frames)
52+
pftrajectories(naive_uint8, ecirc)
53+
pftrajectories(shor_uint8, vcat(ecirc, shor_cat_scirc))
54+
p_uint8 = reinterpret_p(p)
55+
pₙ_uint8 = embed(naive_qubits+1, 1:dataqubits, p_uint8)
56+
pₛ_uint8 = embed(shor_qubits+1, 1:dataqubits, p_uint8)
57+
mul_left!(naive_uint8.frame, pₙ_uint8)
58+
mul_left!(shor_uint8.frame, pₛ_uint8)
59+
pftrajectories(naive_uint8, naive_scirc)
60+
pftrajectories(shor_uint8, shor_scirc)
61+
@test pfmeasurements(shor_uint8)[:,shor_bits] == pfmeasurements(shor_frames)[:,shor_bits] == pfmeasurements(naive_frames) == pfmeasurements(naive_uint8)
4162
end
4263
end
4364

4465
@testset "naive and shor measurement circuits" begin
45-
for c in all_testablable_code_instances()
66+
for (i,c) in enumerate(all_testablable_code_instances())
4667
pframe_naive_vs_shor_syndrome(c)
4768
end
4869
end

test/test_pauliframe.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,29 @@
8888
@test all(0.25.*[1 0 0 0 1] .<= (sum(m, dims=1)[:,1:5])./n .<= 0.75.*[1 0 0 0 1])
8989
end
9090
end
91+
92+
@testset "PauliMeasurements" begin
93+
n = 2000
94+
state = Register(one(MixedDestabilizer, 3), 5)
95+
frame = PauliFrame(n, 3, 5)
96+
97+
glassy_ghz_circuit = [
98+
sHadamard(1), sHadamard(2), sHadamard(3),
99+
PauliMeasurement(P"ZZ_", 1), PauliMeasurement(P"_ZZ", 2),
100+
sMZ(1, 3), sMZ(2, 4), sMZ(3, 5)
101+
]
102+
for m in [pfmeasurements(pftrajectories(copy(frame), glassy_ghz_circuit)),
103+
pfmeasurements(pftrajectories(glassy_ghz_circuit; trajectories=n, threads=false)),
104+
pfmeasurements(pftrajectories(glassy_ghz_circuit; trajectories=n, threads=true))]
105+
106+
# decode based on measurement outcomes
107+
for r in eachrow(m)
108+
r[4] ⊻= r[1]
109+
r[5] ⊻= r[1] r[2]
110+
end
111+
112+
# check that the correct correlations are present
113+
@test all(m[:, 3] .== m[:, 4] .== m[:, 5])
114+
end
115+
end
91116
end

0 commit comments

Comments
 (0)