Skip to content

Commit 1813f26

Browse files
authored
allocation improvements and fixes to decoder dependencies (#479)
1 parent f65dc3c commit 1813f26

File tree

7 files changed

+59
-44
lines changed

7 files changed

+59
-44
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35"
5252
HostCPUFeatures = "0.1.6"
5353
ILog2 = "0.2.3, 1, 2"
5454
InteractiveUtils = "1.9"
55-
LDPCDecoders = "0.3.1"
55+
LDPCDecoders = "0.3.2"
5656
LinearAlgebra = "1.9"
5757
MacroTools = "0.5.9"
5858
Makie = "0.20, 0.21, 0.22"
5959
Nemo = "0.42.1, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48"
6060
Plots = "1.38.0"
6161
PrecompileTools = "1.2"
62-
PyQDecoders = "0.2.1"
62+
PyQDecoders = "0.2.2"
6363
Quantikz = "1.3.1"
6464
QuantumInterface = "0.3.3"
6565
QuantumOpticsBase = "0.4.18, 0.5"

benchmark/benchmarks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ SUITE["ecc"] = BenchmarkGroup(["ecc"])
150150
SUITE["ecc"]["evaluate_decoder"] = BenchmarkGroup(["evaluate_decoder"])
151151
for (cs, c) in [("shor",Shor9()), ("toric8",Toric(8,8))]
152152
for (ds, d) in [
153-
[("table",TableDecoder(c)), ("bp",BeliefPropDecoder(c)), ("pybp",PyBeliefPropDecoder(c))]...,
153+
[("table",TableDecoder(c)), ("bp",BeliefPropDecoder(c)), ("pybp",PyBeliefPropDecoder(c)), ("pybposd",PyBeliefPropOSDecoder(c))]...,
154154
(isa(c,Toric) ? [("pymatch",PyMatchingDecoder(c))] : [])...]
155155
for (ss, s) in [("comm",CommutationCheckECCSetup(0.01)), ("naivesyn",NaiveSyndromeECCSetup(0.01,0)), ("shorsyn",ShorSyndromeECCSetup(0.01,0))]
156156
SUITE["ecc"]["evaluate_decoder"]["$(cs)_$(ds)_$(ss)"] = @benchmarkable evaluate_decoder($d, $s, 1000)

ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,36 @@ struct PyBeliefPropOSDecoder <: PyBP # TODO all these decoders have the same fie
3333
end
3434

3535
function PyBeliefPropDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=nothing)
36-
Hx = parity_checks_x(c) |> collect # TODO should be sparse
37-
Hz = parity_checks_z(c) |> collect # TODO should be sparse
36+
Hx = reinterpret(UInt8,collect(parity_checks_x(c)))
37+
Hz = reinterpret(UInt8,collect(parity_checks_z(c)))
3838
H = parity_checks(c)
3939
fm = faults_matrix(c)
4040
max_iter=isnothing(maxiter) ? 0 : maxiter
41-
bpmethod (nothing, :productsum, :minsum, :minsumlog) || error(lazy"PyBeliefPropDecoder got an unknown belief propagation method argument. `bpmethod` must be one of :productsum, :minsum, :minsumlog.")
42-
bp_method = get(Dict(:productsum => 0, :minsum => 1, :minsumlog => 2), bpmethod, 0)
41+
bpmethod (nothing, :productsum, :minsum) || error(lazy"PyBeliefPropDecoder got an unknown belief propagation method argument. `bpmethod` must be one of :productsum, :minsum.")
42+
bp_method = get(Dict(:productsum => "product_sum", :minsum => "minimum_sum"), bpmethod, "minimum_sum")
4343
isnothing(errorrate) || 0errorrate1 || error(lazy"PyBeliefPropDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].")
44-
error_rate = isnothing(errorrate) ? PythonCall.Py(nothing) : errorrate
45-
pyx = ldpc.bp_decoder(np.array(Hx); max_iter, bp_method, error_rate) # TODO should be sparse
46-
pyz = ldpc.bp_decoder(np.array(Hz); max_iter, bp_method, error_rate) # TODO should be sparse
44+
error_rate = isnothing(errorrate) ? PythonCall.Py(0.0001) : errorrate
45+
pyx = ldpc.BpDecoder(np.array(Hx); max_iter, bp_method, error_rate) # TODO should be sparse
46+
pyz = ldpc.BpDecoder(np.array(Hz); max_iter, bp_method, error_rate) # TODO should be sparse
4747
return PyBeliefPropDecoder(c, H, Hx, Hz, size(Hx, 1), size(Hz, 1), fm, pyx, pyz)
4848
end
4949

5050
function PyBeliefPropOSDecoder(c; maxiter=nothing, bpmethod=nothing, errorrate=nothing, osdmethod=nothing, osdorder=0)
51-
Hx = parity_checks_x(c) |> collect # TODO should be sparse
52-
Hz = parity_checks_z(c) |> collect # TODO should be sparse
51+
Hx = reinterpret(UInt8,collect(parity_checks_x(c)))
52+
Hz = reinterpret(UInt8,collect(parity_checks_z(c)))
5353
H = parity_checks(c)
5454
fm = faults_matrix(c)
5555
max_iter=isnothing(maxiter) ? 0 : maxiter
56-
bpmethod (nothing, :productsum, :minsum, :minsumlog) || error(lazy"PyBeliefPropDecoder got an unknown belief propagation method argument. `bpmethod` must be one of :productsum, :minsum, :minsumlog.")
57-
bp_method = get(Dict(:productsum => 0, :minsum => 1, :minsumlog => 2), bpmethod, 0)
58-
isnothing(errorrate) || 0errorrate1 || error(lazy"PyBeliefPropDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].")
59-
error_rate = isnothing(errorrate) ? PythonCall.Py(nothing) : errorrate
56+
bpmethod (nothing, :productsum, :minsum) || error(lazy"PyBeliefPropOSDecoder got an unknown belief propagation method argument. `bpmethod` must be one of :productsum, :minsum.")
57+
bp_method = get(Dict(:productsum => "product_sum", :minsum => "minimum_sum"), bpmethod, "minimum_sum")
58+
isnothing(errorrate) || 0errorrate1 || error(lazy"PyBeliefPropOSDecoder got an invalid error rate argument. `errorrate` must be in the range [0, 1].")
59+
error_rate = isnothing(errorrate) ? PythonCall.Py(0.0001) : errorrate
6060
isnothing(osdmethod) || osdmethod (:zeroorder, :exhaustive, :combinationsweep) || error(lazy"PyBeliefPropOSDecoder got an unknown OSD method argument. `osdmethod` must be one of :zeroorder, :exhaustive, :combinationsweep.")
61-
osd_method = get(Dict(:zeroorder => "osd0", :exhaustive => "osde", :combinationsweep => "osdcs"), osdmethod, 0)
62-
0osdorder || error(lazy"PyBeliefPropOSDecoder got an invalid OSD order argument. `osdorder` must be ≥0.")
61+
osd_method = get(Dict(:zeroorder => "OSD_0", :exhaustive => "OSD_E", :combinationsweep => "OSD_CS"), osdmethod, 0)
62+
0osdorder || error(lazy"PyBeliefPropOSDecoder got an invalid OSD order argument. `osdorder` must be ≥0.")
6363
osd_order = osdorder
64-
pyx = ldpc.bposd_decoder(np.array(Hx); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse
65-
pyz = ldpc.bposd_decoder(np.array(Hz); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse
64+
pyx = ldpc.BpOsdDecoder(np.array(Hx); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse
65+
pyz = ldpc.BpOsdDecoder(np.array(Hz); max_iter, bp_method, error_rate, osd_method, osd_order) # TODO should be sparse
6666
return PyBeliefPropOSDecoder(c, H, Hx, Hz, size(Hx, 1), size(Hz, 1), fm, pyx, pyz)
6767
end
6868

src/dense_cliffords.jl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ X₁ ⟼ + Z
3939
Z₁ ⟼ + Y
4040
```
4141
"""
42-
struct CliffordOperator{T<:Tableau} <: AbstractCliffordOperator
42+
struct CliffordOperator{T<:Tableau,P<:PauliOperator} <: AbstractCliffordOperator
4343
tab::T
44+
buffer::P
4445
function CliffordOperator(tab::Tableau)
4546
if size(tab,1)==2*size(tab,2)
46-
new{typeof(tab)}(tab)
47+
p = zero!(tab[1])
48+
new{typeof(tab),typeof(p)}(tab,p)
4749
#elseif size(stab,1)==size(stab,2) # TODO be able to work with squara tableaux (by reversing all row operations)
4850
# destab = tab(Destabilizer(stab))
4951
# new{typeof(destab.phases),typeof(destab.xzs)}(destab) # TODO be smarter about type signatures here... there should be a better way
@@ -114,7 +116,7 @@ function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator; phases
114116
nqubits(stab)==nqubits(c) || throw(DimensionMismatch("The tableau and the Clifford operator need to act on the same number of qubits. Consider specifying an array of indices as a third argument to the `apply!` function to avoid this error."))
115117
s_tab = tab(stab)
116118
c_tab = tab(c)
117-
new_stabrow = zero(s_tab[1])
119+
new_stabrow = c.buffer
118120
for row_stab in eachindex(s_tab)
119121
zero!(new_stabrow)
120122
apply_row_kernel!(new_stabrow, row_stab, s_tab, c_tab, phases=Val(phases))
@@ -127,7 +129,7 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator; phases::Val{B}=V
127129
nqubits(stab)==nqubits(c) || throw(DimensionMismatch("The tableau and the Clifford operator need to act on the same number of qubits. Consider specifying an array of indices as a third argument to the `apply!` function to avoid this error."))
128130
s_tab = tab(stab)
129131
c_tab = tab(c)
130-
threadlocal=zero(c_tab[1])::PauliOperator # typeassert for JET
132+
threadlocal = c.buffer
131133
@inbounds for row_stab in eachindex(s_tab)
132134
zero!(threadlocal) # a new stabrow for temporary storage
133135
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, phases=phases)
@@ -159,7 +161,7 @@ end
159161
function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator, indices_of_application::AbstractArray{Int,1}; phases::Bool=true)
160162
s_tab = tab(stab)
161163
c_tab = tab(c)
162-
new_stabrow = zero(PauliOperator,nqubits(c))
164+
new_stabrow = c.buffer
163165
for row in eachindex(s_tab)
164166
zero!(new_stabrow)
165167
apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases=Val(phases))
@@ -172,7 +174,7 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator, indices_of_appli
172174
#max(indices_of_application)<=nqubits(s) || throw(DimensionMismatch("")) # Too expensive to check every time
173175
s_tab = tab(stab)
174176
c_tab = tab(c)
175-
threadlocal=zero(c_tab[1])::PauliOperator # typeassert for JET
177+
threadlocal= c.buffer
176178
@inbounds for row_stab in eachindex(s_tab)
177179
zero!(threadlocal) # a new stabrow for temporary storage
178180
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, indices_of_application, phases=phases)

src/linalg.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ trusted_rank(s::Destabilizer) = length(s)
111111
trusted_rank(s::MixedStabilizer) = LinearAlgebra.rank(s)
112112
trusted_rank(s::MixedDestabilizer) = LinearAlgebra.rank(s)
113113

114-
"""Tensor product between operators or tableaux.
114+
"""Tensor product between operators or tableaux.
115115
116116
Tensor product between CiffordOperators:
117117
@@ -221,8 +221,8 @@ end
221221

222222
function tensor(ops::Stabilizer...)
223223
length(ops)==1 && return ops[1]
224-
ntot = sum(nqubits, ops)
225-
rtot = sum(length, ops)
224+
ntot = sum(nqubits, ops) # TODO why is this allocating (at least in 1.11)
225+
rtot = sum(length, ops) # TODO why is this allocating (at least in 1.11)
226226
tab = zero(Stabilizer, rtot, ntot)
227227
last_row = 0
228228
last_col = 0

src/symbolic_cliffords.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Z₂ ⟼ - _X_
132132
Z₃ ⟼ + __Z
133133
134134
julia> typeof(t_op)
135-
CliffordOperator{QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}}
135+
CliffordOperator{QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}, PauliOperator{Array{UInt8, 0}, Vector{UInt64}}}
136136
137137
julia> CliffordOperator(op, 1, compact=true) # You can also extract just the non-trivial part of the tableau
138138
X₁ ⟼ - Y

test/test_allocations.jl

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
@testitem "Allocation checks" begin
1+
@testitem "Allocation checks" tags=[:alloccc] begin
2+
using QuantumClifford
23
using QuantumClifford: mul_left!
34
n = Threads.nthreads()
4-
allocated(f::F) where {F} = @allocated f()
5+
allocated(f::F) where {F} = @allocations f()
56
@testset "apply! mul_left! canonicalize!" begin
67
p1 = random_pauli(500)
78
p2 = random_pauli(500)
@@ -13,28 +14,34 @@
1314
f2() = canonicalize!(s)
1415
f2()
1516
allocated(f2)
16-
@test allocated(f2) < 70
17+
@test allocated(f2) <= 1
1718
f2a() = QuantumClifford._canonicalize!(s)
1819
f2a()
1920
allocated(f2a)
20-
@test allocated(f2a) < 40
21+
@test allocated(f2a) <= 1
2122
c = random_clifford(500)
2223
f3() = apply!(s,c)
2324
f3()
24-
@test allocated(f3) < 1500*n # TODO lower it by making apply! more efficient
25+
allocated(f3)
26+
#@test allocated(f3) <= 1 # TODO lower it by making apply! more efficient
27+
@test_broken false # the test above does not always work on julia 1.11+, depending on whether it runs in CI or not
2528
f4() = apply!(s,tCNOT,[5,20])
2629
f4()
27-
@test allocated(f4) < 1500*n # TODO lower it by making apply! more efficient
30+
allocated(f4)
31+
#@test allocated(f4) <= 3 # TODO lower it by making apply! more efficient
32+
@test_broken false # the test above does not always work on julia 1.11+, depending on whether it runs in CI or not
2833
for phases in [(false,false),(false,true),(true,false),(true,true)], i in 1:6
2934
g = enumerate_single_qubit_gates(i,qubit=10,phases=phases)
3035
f5() = apply!(s,g)
3136
f5()
32-
@test allocated(f5) < 130*n
37+
allocated(f5)
38+
@test allocated(f5) <= 2
3339
end
3440
for g in [sSWAP(10,200), sCNOT(10,200)]
3541
f6() = apply!(s,g)
3642
f6()
37-
@test allocated(f6) < 170*n
43+
allocated(f6)
44+
@test allocated(f6) <= 2
3845
end
3946
end
4047
@testset "project!" begin
@@ -54,22 +61,28 @@
5461
f3()
5562
f4() = project!(md,p)
5663
f4()
57-
@test allocated(f1) < 1600
58-
@test allocated(f2) < 1500
59-
@test allocated(f3) < 400
60-
@test allocated(f4) < 450
64+
allocated(f1)
65+
allocated(f2)
66+
allocated(f3)
67+
allocated(f4)
68+
@test allocated(f1) <= 15
69+
@test allocated(f2) <= 12
70+
@test allocated(f3) <= 6
71+
@test allocated(f4) <= 8
6172
for p! in [projectX!, projectY!, projectZ!]
6273
md = MixedDestabilizer(random_destabilizer(N))
6374
md.rank = 50
6475
f5() = p!(md,40)
6576
f5()
66-
@test allocated(f5) < 300
77+
allocated(f5)
78+
@test allocated(f5) <= 7
6779
end
6880
end
6981
@testset "tensor product" begin
7082
stabs = [s[1:5] for s in [random_stabilizer(n) for n in [63,64,65,127,128,129]]]
7183
f1() = (stabs...)
7284
f1()
73-
@test allocated(f1) < 6000
85+
allocated(f1)
86+
@test allocated(f1) <= 18
7487
end
7588
end

0 commit comments

Comments
 (0)