Skip to content

allocation improvements #479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35"
HostCPUFeatures = "0.1.6"
ILog2 = "0.2.3, 1, 2"
InteractiveUtils = "1.9"
LDPCDecoders = "0.3.1"
LDPCDecoders = "0.3.2"
LinearAlgebra = "1.9"
MacroTools = "0.5.9"
Makie = "0.20, 0.21, 0.22"
Nemo = "0.42.1, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48"
Plots = "1.38.0"
PrecompileTools = "1.2"
PyQDecoders = "0.2.1"
PyQDecoders = "0.2.2"
Quantikz = "1.3.1"
QuantumInterface = "0.3.3"
QuantumOpticsBase = "0.4.18, 0.5"
Expand Down
2 changes: 1 addition & 1 deletion benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ SUITE["ecc"] = BenchmarkGroup(["ecc"])
SUITE["ecc"]["evaluate_decoder"] = BenchmarkGroup(["evaluate_decoder"])
for (cs, c) in [("shor",Shor9()), ("toric8",Toric(8,8))]
for (ds, d) in [
[("table",TableDecoder(c)), ("bp",BeliefPropDecoder(c)), ("pybp",PyBeliefPropDecoder(c))]...,
[("table",TableDecoder(c)), ("bp",BeliefPropDecoder(c)), ("pybp",PyBeliefPropDecoder(c)), ("pybposd",PyBeliefPropOSDecoder(c))]...,
(isa(c,Toric) ? [("pymatch",PyMatchingDecoder(c))] : [])...]
for (ss, s) in [("comm",CommutationCheckECCSetup(0.01)), ("naivesyn",NaiveSyndromeECCSetup(0.01,0)), ("shorsyn",ShorSyndromeECCSetup(0.01,0))]
SUITE["ecc"]["evaluate_decoder"]["$(cs)_$(ds)_$(ss)"] = @benchmarkable evaluate_decoder($d, $s, 1000)
Expand Down
34 changes: 17 additions & 17 deletions ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,36 @@ struct PyBeliefPropOSDecoder <: PyBP # TODO all these decoders have the same fie
end

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

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

Expand Down
14 changes: 8 additions & 6 deletions src/dense_cliffords.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ X₁ ⟼ + Z
Z₁ ⟼ + Y
```
"""
struct CliffordOperator{T<:Tableau} <: AbstractCliffordOperator
struct CliffordOperator{T<:Tableau,P<:PauliOperator} <: AbstractCliffordOperator
tab::T
buffer::P
function CliffordOperator(tab::Tableau)
if size(tab,1)==2*size(tab,2)
new{typeof(tab)}(tab)
p = zero!(tab[1])
new{typeof(tab),typeof(p)}(tab,p)
#elseif size(stab,1)==size(stab,2) # TODO be able to work with squara tableaux (by reversing all row operations)
# destab = tab(Destabilizer(stab))
# new{typeof(destab.phases),typeof(destab.xzs)}(destab) # TODO be smarter about type signatures here... there should be a better way
Expand Down Expand Up @@ -114,7 +116,7 @@ function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator; phases
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."))
s_tab = tab(stab)
c_tab = tab(c)
new_stabrow = zero(s_tab[1])
new_stabrow = c.buffer
for row_stab in eachindex(s_tab)
zero!(new_stabrow)
apply_row_kernel!(new_stabrow, row_stab, s_tab, c_tab, phases=Val(phases))
Expand All @@ -127,7 +129,7 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator; phases::Val{B}=V
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."))
s_tab = tab(stab)
c_tab = tab(c)
threadlocal=zero(c_tab[1])::PauliOperator # typeassert for JET
threadlocal = c.buffer
@inbounds for row_stab in eachindex(s_tab)
zero!(threadlocal) # a new stabrow for temporary storage
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, phases=phases)
Expand Down Expand Up @@ -159,7 +161,7 @@ end
function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator, indices_of_application::AbstractArray{Int,1}; phases::Bool=true)
s_tab = tab(stab)
c_tab = tab(c)
new_stabrow = zero(PauliOperator,nqubits(c))
new_stabrow = c.buffer
for row in eachindex(s_tab)
zero!(new_stabrow)
apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases=Val(phases))
Expand All @@ -172,7 +174,7 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator, indices_of_appli
#max(indices_of_application)<=nqubits(s) || throw(DimensionMismatch("")) # Too expensive to check every time
s_tab = tab(stab)
c_tab = tab(c)
threadlocal=zero(c_tab[1])::PauliOperator # typeassert for JET
threadlocal= c.buffer
@inbounds for row_stab in eachindex(s_tab)
zero!(threadlocal) # a new stabrow for temporary storage
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, indices_of_application, phases=phases)
Expand Down
6 changes: 3 additions & 3 deletions src/linalg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ trusted_rank(s::Destabilizer) = length(s)
trusted_rank(s::MixedStabilizer) = LinearAlgebra.rank(s)
trusted_rank(s::MixedDestabilizer) = LinearAlgebra.rank(s)

"""Tensor product between operators or tableaux.
"""Tensor product between operators or tableaux.

Tensor product between CiffordOperators:

Expand Down Expand Up @@ -221,8 +221,8 @@ end

function tensor(ops::Stabilizer...)
length(ops)==1 && return ops[1]
ntot = sum(nqubits, ops)
rtot = sum(length, ops)
ntot = sum(nqubits, ops) # TODO why is this allocating (at least in 1.11)
rtot = sum(length, ops) # TODO why is this allocating (at least in 1.11)
tab = zero(Stabilizer, rtot, ntot)
last_row = 0
last_col = 0
Expand Down
2 changes: 1 addition & 1 deletion src/symbolic_cliffords.jl
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Z₂ ⟼ - _X_
Z₃ ⟼ + __Z

julia> typeof(t_op)
CliffordOperator{QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}}
CliffordOperator{QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}, PauliOperator{Array{UInt8, 0}, Vector{UInt64}}}

julia> CliffordOperator(op, 1, compact=true) # You can also extract just the non-trivial part of the tableau
X₁ ⟼ - Y
Expand Down
41 changes: 27 additions & 14 deletions test/test_allocations.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
@testitem "Allocation checks" begin
@testitem "Allocation checks" tags=[:alloccc] begin
using QuantumClifford
using QuantumClifford: mul_left!
n = Threads.nthreads()
allocated(f::F) where {F} = @allocated f()
allocated(f::F) where {F} = @allocations f()
@testset "apply! mul_left! canonicalize!" begin
p1 = random_pauli(500)
p2 = random_pauli(500)
Expand All @@ -13,28 +14,34 @@
f2() = canonicalize!(s)
f2()
allocated(f2)
@test allocated(f2) < 70
@test allocated(f2) <= 1
f2a() = QuantumClifford._canonicalize!(s)
f2a()
allocated(f2a)
@test allocated(f2a) < 40
@test allocated(f2a) <= 1
c = random_clifford(500)
f3() = apply!(s,c)
f3()
@test allocated(f3) < 1500*n # TODO lower it by making apply! more efficient
allocated(f3)
#@test allocated(f3) <= 1 # TODO lower it by making apply! more efficient
@test_broken false # the test above does not always work on julia 1.11+, depending on whether it runs in CI or not
f4() = apply!(s,tCNOT,[5,20])
f4()
@test allocated(f4) < 1500*n # TODO lower it by making apply! more efficient
allocated(f4)
#@test allocated(f4) <= 3 # TODO lower it by making apply! more efficient
@test_broken false # the test above does not always work on julia 1.11+, depending on whether it runs in CI or not
for phases in [(false,false),(false,true),(true,false),(true,true)], i in 1:6
g = enumerate_single_qubit_gates(i,qubit=10,phases=phases)
f5() = apply!(s,g)
f5()
@test allocated(f5) < 130*n
allocated(f5)
@test allocated(f5) <= 2
end
for g in [sSWAP(10,200), sCNOT(10,200)]
f6() = apply!(s,g)
f6()
@test allocated(f6) < 170*n
allocated(f6)
@test allocated(f6) <= 2
end
end
@testset "project!" begin
Expand All @@ -54,22 +61,28 @@
f3()
f4() = project!(md,p)
f4()
@test allocated(f1) < 1600
@test allocated(f2) < 1500
@test allocated(f3) < 400
@test allocated(f4) < 450
allocated(f1)
allocated(f2)
allocated(f3)
allocated(f4)
@test allocated(f1) <= 15
@test allocated(f2) <= 12
@test allocated(f3) <= 6
@test allocated(f4) <= 8
for p! in [projectX!, projectY!, projectZ!]
md = MixedDestabilizer(random_destabilizer(N))
md.rank = 50
f5() = p!(md,40)
f5()
@test allocated(f5) < 300
allocated(f5)
@test allocated(f5) <= 7
end
end
@testset "tensor product" begin
stabs = [s[1:5] for s in [random_stabilizer(n) for n in [63,64,65,127,128,129]]]
f1() = ⊗(stabs...)
f1()
@test allocated(f1) < 6000
allocated(f1)
@test allocated(f1) <= 18
end
end
Loading