Skip to content

Commit e8f94e9

Browse files
authored
Non-allocating project! thanks to Val{Bool} in mul_left! (#41)
Fixes #39
1 parent f12a38f commit e8f94e9

File tree

4 files changed

+171
-120
lines changed

4 files changed

+171
-120
lines changed

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ BenchmarkTools.Trial: 564 samples with 1 evaluation.
114114
Memory estimate: 13.84 KiB, allocs estimate: 111.
115115
```
116116

117-
#### Sparse gate application to only specified qubits in a 1000 qubit tableau in 4 microseconds
117+
#### Sparse gate application to only specified qubits in a 1000 qubit tableau in 4 μs
118118

119119
```jldoctest
120120
julia> @benchmark apply!(s, sCNOT(32,504)) setup=(s=random_stabilizer(1000))
@@ -130,4 +130,40 @@ BenchmarkTools.Trial: 10000 samples with 9 evaluations.
130130
Memory estimate: 96 bytes, allocs estimate: 2.
131131
```
132132

133+
#### Measuring a dense 1000 qubit Pauli operator in 74 μs
134+
135+
```jldoctest
136+
julia> s=random_destabilizer(1000); p=random_pauli(1000);
137+
138+
julia> @benchmark project!(_s,_p) setup=(_s=copy(s);_p=copy(p)) evals=1
139+
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
140+
Range (min … max): 69.030 μs … 144.963 μs ┊ GC (min … max): 0.00% … 0.00%
141+
Time (median): 73.799 μs ┊ GC (median): 0.00%
142+
Time (mean ± σ): 73.639 μs ± 4.118 μs ┊ GC (mean ± σ): 0.00% ± 0.00%
143+
144+
▂ ▁█▁
145+
▃██▆▄▃▃▃▄▆▅▄▃▃███▃▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
146+
69 μs Histogram: frequency by time 92.8 μs <
147+
148+
Memory estimate: 480 bytes, allocs estimate: 4.
149+
```
150+
151+
#### Measuring a single qubit in a 1000 qubit tableau in 50 μs
152+
153+
```jldoctest
154+
julia> s=MixedDestabilizer(random_destabilizer(1000));
155+
156+
julia> @benchmark projectY!(_s,42) setup=(_s=copy(s)) evals=1
157+
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
158+
Range (min … max): 46.928 μs … 88.046 μs ┊ GC (min … max): 0.00% … 0.00%
159+
Time (median): 49.934 μs ┊ GC (median): 0.00%
160+
Time (mean ± σ): 49.776 μs ± 2.623 μs ┊ GC (mean ± σ): 0.00% ± 0.00%
161+
162+
▁ ▄█▂
163+
▂▂▆██▄▄▄▄▆███▅▃▂▂▂▂▂▂▂▂▂▂▂▂▂▁▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▁▂ ▃
164+
46.9 μs Histogram: frequency by time 63.8 μs <
165+
166+
Memory estimate: 464 bytes, allocs estimate: 5.
167+
```
168+
133169
Benchmarks executed on a Ryzen Zen1 8-core CPU.

src/QuantumClifford.jl

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -743,8 +743,8 @@ function _mul_left_nonvec!(r::AbstractVector{T}, l::AbstractVector{T}; phases::B
743743
s
744744
end
745745

746-
function mul_left!(r::AbstractVector{T}, l::AbstractVector{T}; phases::Bool=true)::UInt8 where T<:Unsigned
747-
if !phases
746+
function mul_left!(r::AbstractVector{T}, l::AbstractVector{T}; phases::Val{B}=Val(true))::UInt8 where {T<:Unsigned, B}
747+
if !B
748748
r .⊻= l
749749
return UInt8(0x0)
750750
end
@@ -789,16 +789,16 @@ function mul_left!(r::AbstractVector{T}, l::AbstractVector{T}; phases::Bool=true
789789
UInt8((rcnt1 (rcnt2<<1))&0x3)
790790
end
791791

792-
@inline function mul_left!(r::PauliOperator, l::PauliOperator; phases::Bool=true)
792+
@inline function mul_left!(r::PauliOperator, l::PauliOperator; phases::Val{B}=Val(true)) where B
793793
nqubits(l)==nqubits(r) || throw(DimensionMismatch("The two Pauli operators should have the same length!")) # TODO skip this when @inbounds is set
794794
s = mul_left!(r.xz, l.xz, phases=phases)
795-
phases && (r.phase[] = (s+r.phase[]+l.phase[])&0x3)
795+
B && (r.phase[] = (s+r.phase[]+l.phase[])&0x3)
796796
r
797797
end
798798

799-
@inline function mul_left!(r::PauliOperator, l::Stabilizer, i::Int; phases::Bool=true)
799+
@inline function mul_left!(r::PauliOperator, l::Stabilizer, i::Int; phases::Val{B}=Val(true)) where B
800800
s = mul_left!(r.xz, (@view l.xzs[:,i]), phases=phases)
801-
phases && (r.phase[] = (s+r.phase[]+l.phases[i])&0x3)
801+
B && (r.phase[] = (s+r.phase[]+l.phases[i])&0x3)
802802
r
803803
end
804804

@@ -861,24 +861,24 @@ end
861861
rowswap!(s.tab, i+n, j+n; phases=phases)
862862
end
863863

864-
@inline function mul_left!(s::Stabilizer, m, i; phases::Bool=true)
864+
@inline function mul_left!(s::Stabilizer, m, i; phases::Val{B}=Val(true)) where B
865865
extra_phase = mul_left!((@view s.xzs[:,m]), (@view s.xzs[:,i]); phases=phases)
866-
phases && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3)
866+
B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3)
867867
s
868868
end
869869

870-
@inline function mul_left!(s::Destabilizer, i, j; phases::Bool=true)
871-
mul_left!(s.tab, j, i; phases=false) # Indices are flipped to preserve commutation constraints
870+
@inline function mul_left!(s::Destabilizer, i, j; phases::Val{B}=Val(true)) where B
871+
mul_left!(s.tab, j, i; phases=Val(false)) # Indices are flipped to preserve commutation constraints
872872
n = size(s.tab,1)÷2
873873
mul_left!(s.tab, i+n, j+n; phases=phases)
874874
end
875875

876-
@inline function mul_left!(s::MixedStabilizer, i, j; phases::Bool=true)
876+
@inline function mul_left!(s::MixedStabilizer, i, j; phases::Val{B}=Val(true)) where B
877877
mul_left!(s.tab, i, j; phases=phases)
878878
end
879879

880-
@inline function mul_left!(s::MixedDestabilizer, i, j; phases::Bool=true)
881-
mul_left!(s.tab, j, i; phases=false) # Indices are flipped to preserve commutation constraints
880+
@inline function mul_left!(s::MixedDestabilizer, i, j; phases::Val{B}=Val(true)) where B
881+
mul_left!(s.tab, j, i; phases=Val(false)) # Indices are flipped to preserve commutation constraints
882882
n = nqubits(s)
883883
mul_left!(s.tab, i+n, j+n; phases=phases)
884884
end
@@ -1028,6 +1028,7 @@ Based on [garcia2012efficient](@cite).
10281028
See also: [`canonicalize_rref!`](@ref), [`canonicalize_gott!`](@ref)
10291029
"""
10301030
function canonicalize!(state::AbstractStabilizer; phases::Bool=true, ranks::Bool=false)
1031+
_phases = Val(phases)
10311032
xzs = stabilizerview(state).xzs
10321033
xs = @view xzs[1:end÷2,:]
10331034
zs = @view xzs[end÷2+1:end,:]
@@ -1047,7 +1048,7 @@ function canonicalize!(state::AbstractStabilizer; phases::Bool=true, ranks::Bool
10471048
rowswap!(state, k, i; phases=phases)
10481049
for m in 1:rows
10491050
if xs[jbig,m]&jsmall!=zerobit && m!=i # if X or Y
1050-
mul_left!(state, m, i; phases=phases)
1051+
mul_left!(state, m, i; phases=_phases)
10511052
end
10521053
end
10531054
i += 1
@@ -1065,7 +1066,7 @@ function canonicalize!(state::AbstractStabilizer; phases::Bool=true, ranks::Bool
10651066
rowswap!(state, k, i; phases=phases)
10661067
for m in 1:rows
10671068
if zs[jbig,m]&jsmall!=zerobit && m!=i # if Z or Y
1068-
mul_left!(state, m, i; phases=phases)
1069+
mul_left!(state, m, i; phases=_phases)
10691070
end
10701071
end
10711072
i += 1
@@ -1094,6 +1095,7 @@ Based on [audenaert2005entanglement](@cite).
10941095
See also: [`canonicalize!`](@ref), [`canonicalize_gott!`](@ref)
10951096
"""
10961097
function canonicalize_rref!(state::AbstractStabilizer, colindices; phases::Bool=true)
1098+
_phases = Val(phases)
10971099
xzs = stabilizerview(state).xzs
10981100
xs = @view xzs[1:end÷2,:]
10991101
zs = @view xzs[end÷2+1:end,:]
@@ -1111,7 +1113,7 @@ function canonicalize_rref!(state::AbstractStabilizer, colindices; phases::Bool=
11111113
rowswap!(state, k, i; phases=phases)
11121114
for m in 1:rows
11131115
if xs[jbig,m]&jsmall!=zerobit && m!=i # if X or Y
1114-
mul_left!(state, m, i; phases=phases)
1116+
mul_left!(state, m, i; phases=_phases)
11151117
end
11161118
end
11171119
i -= 1
@@ -1122,7 +1124,7 @@ function canonicalize_rref!(state::AbstractStabilizer, colindices; phases::Bool=
11221124
rowswap!(state, k, i; phases=phases)
11231125
for m in 1:rows
11241126
if zs[jbig,m]&jsmall!=zerobit && m!=i # if Z or Y
1125-
mul_left!(state, m, i; phases=phases)
1127+
mul_left!(state, m, i; phases=_phases)
11261128
end
11271129
end
11281130
i -= 1
@@ -1178,6 +1180,7 @@ Based on [gottesman1997stabilizer](@cite).
11781180
See also: [`canonicalize!`](@ref), [`canonicalize_rref!`](@ref)
11791181
"""
11801182
function canonicalize_gott!(stabilizer::Stabilizer{Tzv,Tm}; phases::Bool=true) where {Tzv<:AbstractVector{UInt8}, Tme<:Unsigned, Tm<:AbstractMatrix{Tme}}
1183+
_phases = Val(phases)
11811184
xzs = stabilizer.xzs
11821185
xs = @view xzs[1:end÷2,:]
11831186
zs = @view xzs[end÷2+1:end,:]
@@ -1196,7 +1199,7 @@ function canonicalize_gott!(stabilizer::Stabilizer{Tzv,Tm}; phases::Bool=true) w
11961199
rowswap!(stabilizer, k, i; phases=phases)
11971200
for m in 1:rows
11981201
if xs[jbig,m]&jsmall!=zerobit && m!=i # if X or Y
1199-
mul_left!(stabilizer, m, i; phases=phases)
1202+
mul_left!(stabilizer, m, i; phases=_phases)
12001203
end
12011204
end
12021205
i += 1
@@ -1216,7 +1219,7 @@ function canonicalize_gott!(stabilizer::Stabilizer{Tzv,Tm}; phases::Bool=true) w
12161219
rowswap!(stabilizer, k, i; phases=phases)
12171220
for m in 1:rows
12181221
if zs[jbig,m]&jsmall!=zerobit && m!=i # if Z or Y
1219-
mul_left!(stabilizer, m, i; phases=phases)
1222+
mul_left!(stabilizer, m, i; phases=_phases)
12201223
end
12211224
end
12221225
i += 1
@@ -1427,7 +1430,7 @@ function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator; phases
14271430
new_stabrow = zero(s_tab[1])
14281431
for row_stab in eachindex(s_tab)
14291432
zero!(new_stabrow)
1430-
apply_row_kernel!(new_stabrow, row_stab, s_tab, c_tab, phases=phases)
1433+
apply_row_kernel!(new_stabrow, row_stab, s_tab, c_tab, phases=Val(phases))
14311434
end
14321435
stab
14331436
end
@@ -1439,19 +1442,19 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator; phases::Val{B}=V
14391442
c_tab = tab(c)
14401443
@batch minbatch=25 threadlocal=zero(c_tab[1]) for row_stab in eachindex(s_tab)
14411444
zero!(threadlocal) # a new stabrow for temporary storage
1442-
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, phases=B)
1445+
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, phases=phases)
14431446
end
14441447
stab
14451448
end
14461449

14471450
# TODO Added a lot of type assertions to help Julia infer types, but they are much too strict for cases where bitpacking varies (check tests)
14481451
#@inline function apply_row_kernel!(new_stabrow::PauliOperator{Array{UInt8,0},Vector{Tme}}, row::Int, s_tab::Stabilizer{Tv,Tm}, c_tab::Stabilizer{Tv,Tm}; phases=true) where {Tme,Tv<:AbstractVector{UInt8},Tm<:AbstractMatrix{Tme}}
1449-
@inline function apply_row_kernel!(new_stabrow, row, s_tab, c_tab; phases=true)
1450-
phases && (new_stabrow.phase[] = s_tab.phases[row])
1452+
@inline function apply_row_kernel!(new_stabrow, row, s_tab, c_tab; phases::Val{B}=Val(true)) where B
1453+
B && (new_stabrow.phase[] = s_tab.phases[row])
14511454
n = nqubits(c_tab)
14521455
for qubit in 1:n
14531456
x,z = s_tab[row,qubit]
1454-
if phases&&x&&z
1457+
if B&&x&&z
14551458
new_stabrow.phase[] -= 0x1
14561459
end
14571460
if x
@@ -1472,7 +1475,7 @@ function _apply_nonthread!(stab::AbstractStabilizer, c::CliffordOperator, indice
14721475
new_stabrow = zero(PauliOperator,nqubits(c))
14731476
for row in eachindex(s_tab)
14741477
zero!(new_stabrow)
1475-
apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases=phases)
1478+
apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases=Val(phases))
14761479
end
14771480
stab
14781481
end
@@ -1484,17 +1487,17 @@ function _apply!(stab::AbstractStabilizer, c::CliffordOperator, indices_of_appli
14841487
c_tab = tab(c)
14851488
@batch minbatch=25 threadlocal=zero(c_tab[1]) for row_stab in eachindex(s_tab)
14861489
zero!(threadlocal) # a new stabrow for temporary storage
1487-
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, indices_of_application, phases=B)
1490+
apply_row_kernel!(threadlocal, row_stab, s_tab, c_tab, indices_of_application, phases=phases)
14881491
end
14891492
stab
14901493
end
14911494

1492-
@inline function apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases=true)
1493-
phases && (new_stabrow.phase[] = s_tab.phases[row])
1495+
@inline function apply_row_kernel!(new_stabrow, row, s_tab, c_tab, indices_of_application; phases::Val{B}=Val(true)) where B
1496+
B && (new_stabrow.phase[] = s_tab.phases[row])
14941497
n = nqubits(c_tab)
14951498
for (qubit_i, qubit) in enumerate(indices_of_application)
14961499
x,z = s_tab[row,qubit]
1497-
if phases&&x&&z
1500+
if B&&x&&z
14981501
new_stabrow.phase[] -= 0x1
14991502
end
15001503
if x
@@ -1507,7 +1510,7 @@ end
15071510
for (qubit_i, qubit) in enumerate(indices_of_application)
15081511
s_tab[row,qubit] = new_stabrow[qubit_i]
15091512
end
1510-
phases && (s_tab.phases[row] = new_stabrow.phase[])
1513+
B && (s_tab.phases[row] = new_stabrow.phase[])
15111514
new_stabrow
15121515
end
15131516

0 commit comments

Comments
 (0)