Skip to content

Commit da8a57d

Browse files
Implement Tableaux randomisation and Adapt.jl extension. (#579)
* Implement Tableaux randomisation and Adapt.jl extension. * Improved the documentation for random_tableau, expanded the test_random suite to include tableaux randomisation, and modified GPU canonicalization tests to eliminate call to random_stabilizer. * changelog --------- Co-authored-by: Stefan Krastanov <github.acc@krastanov.org> Co-authored-by: Stefan Krastanov <stefan@krastanov.org>
1 parent 021b8a8 commit da8a57d

File tree

12 files changed

+272
-22
lines changed

12 files changed

+272
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
## v0.10.1-dev
99

10+
- Adapt.jl can now be used to convert various types to GPU-backed storage.
1011
- The `GeneralizedBicycleCode` and `ExtendedGeneralizedBicycleCode` are now implemented via `Hecke`'s polynomial ring in the ECC submodule.
1112
- Add `apply_right!` that applies a clifford operator to the right of a dense clifford operator.
1213
- Add `mul_right!` methods for inplace operations between tableaus

Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2424
SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2"
2525

2626
[weakdeps]
27+
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
2728
Atomix = "a9b6321e-bd34-4604-b9c9-b65b8de01458"
2829
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
2930
GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
@@ -42,6 +43,7 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678"
4243
path = "lib/QECCore"
4344

4445
[extensions]
46+
QuantumCliffordAdaptExt = ["Adapt", "GPUArraysCore"]
4547
QuantumCliffordGPUExt = "CUDA"
4648
QuantumCliffordHeckeExt = "Hecke"
4749
QuantumCliffordJuMPExt = "JuMP"
@@ -55,6 +57,7 @@ QuantumCliffordQOpticsExt = "QuantumOpticsBase"
5557
QuantumCliffordQuantikzExt = "Quantikz"
5658

5759
[compat]
60+
Adapt = "4.3.0"
5861
Atomix = "1.1.1"
5962
CUDA = "4.4.0, 5"
6063
Combinatorics = "1.0"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
#=============================================================================#
3+
module QuantumCliffordAdaptExt
4+
5+
include("imports.jl")
6+
include("../QuantumCliffordKAExt/definitions.jl")
7+
include("utilities.jl")
8+
include("adapters.jl")
9+
10+
end
11+
#=============================================================================#
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Adapt Extension
2+
3+
This directory contains the implementation for the Adapt derived functionality that enables smooth interoperation between objects residing in either the host or the device memory spaces. Please consult the JuliaGPU documentation for comprehensive information on how to setup and configure any specific device.
4+
5+
# Requirements
6+
7+
The following packages must be imported in order to activate this extension:
8+
- [Adapt](https://github.com/JuliaGPU/Adapt.jl)
9+
- [GPUArraysCore](https://github.com/JuliaGPU/GPUArrays.jl)
10+
11+
To benefit from the specialised execution path(s) available via dispatching to the underlying hardware accelerators, the [KernelAbstractions](https://github.com/JuliaGPU/KernelAbstractions.jl) extension must be activated to enable those features.
12+
13+
# Noteworthy Details
14+
15+
- In order to support the utilisation of this extension in conjunction with the KernelAbstractions invocations, the bitwidth of the phase variable(s) must be compatible with the usage of atomic intrinsics. This is handled automatically by the adapt function calls but it introduces an incredibly minute discrepancy in the storage requirements depending on where the objects' memory is located.
16+
17+
# Warnings
18+
19+
The features provided herein remain an early and incomplete work-in-progress that is subject to continuous development. Bugs, missing features, and breaking changes are to be expected until such a time as when it is deemed suitable for official release. Consider this to be a thorough warning that **HERE BE DRAGONS**.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
#=============================================================================#
3+
# PauliOperator
4+
function adapt_structure(
5+
AT::Type{T}, pauli::PauliOperator
6+
) where {T <: AbstractArray}
7+
8+
return PauliOperator(
9+
adapt(AT, change_type(UInt8, pauli.phase)),
10+
pauli.nqubits,
11+
adapt(AT, pauli.xz)
12+
)
13+
14+
end
15+
16+
function adapt_structure(
17+
AT::Type{T}, pauli::PauliOperator
18+
) where {T <: AbstractGPUArray}
19+
20+
return PauliOperator(
21+
adapt(AT, change_type(DeviceUnsigned, pauli.phase)),
22+
pauli.nqubits,
23+
adapt(AT, pauli.xz)
24+
)
25+
26+
end
27+
28+
# Tableau
29+
function adapt_structure(
30+
AT::Type{T}, tab::Tableau
31+
) where {T <: AbstractArray}
32+
33+
return Tableau(
34+
adapt(AT, change_type(UInt8, tab.phases)),
35+
tab.nqubits,
36+
adapt(AT, tab.xzs)
37+
)
38+
39+
end
40+
41+
function adapt_structure(
42+
AT::Type{T}, tab::Tableau
43+
) where {T <: AbstractGPUArray}
44+
45+
return Tableau(
46+
adapt(AT, change_type(DeviceUnsigned, tab.phases)),
47+
tab.nqubits,
48+
adapt(AT, tab.xzs)
49+
)
50+
51+
end
52+
53+
# Stabilizer
54+
function adapt_structure(
55+
AT::Type{T}, state::Stabilizer
56+
) where {T <: AbstractArray}
57+
58+
return Stabilizer(adapt(AT, state.tab))
59+
60+
end
61+
62+
# Destabilizer
63+
function adapt_structure(
64+
AT::Type{T}, state::Destabilizer
65+
) where {T <: AbstractArray}
66+
67+
return Destabilizer(adapt(AT, state.tab))
68+
69+
end
70+
71+
# MixedStabilizer
72+
function adapt_structure(
73+
AT::Type{T}, state::MixedStabilizer
74+
) where {T <: AbstractArray}
75+
76+
return MixedStabilizer(adapt(AT, state.tab), state.rank)
77+
78+
end
79+
80+
# MixedDestabilizer
81+
function adapt_structure(
82+
AT::Type{T}, state::MixedDestabilizer
83+
) where {T <: AbstractArray}
84+
85+
return MixedDestabilizer(adapt(AT, state.tab), state.rank)
86+
87+
end
88+
#=============================================================================#
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
#=============================================================================#
3+
import Adapt: adapt_structure
4+
5+
using Adapt: adapt
6+
7+
using GPUArraysCore: AbstractGPUArray
8+
9+
using QuantumClifford
10+
# This must be done explicitly as it is not exported.
11+
using QuantumClifford: Tableau
12+
#=============================================================================#
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
#=============================================================================#
3+
# CAUTION: Requires an integral ratio between the sizes of the data types.
4+
@inline function change_type(
5+
::Type{T}, source::AbstractArray{S}; interpret::Bool = false
6+
) where {T <: Unsigned, S <: Unsigned}
7+
8+
if T == S
9+
output = copy(source)
10+
elseif !interpret || size(source) == ()
11+
output = map(x -> T(x), source)
12+
elseif sizeof(S) >= sizeof(T)
13+
output = copy(reinterpret(T, source))
14+
else
15+
len, dims... = size(source)
16+
new_len = cld(len, div(sizeof(T), sizeof(S)))
17+
output = similar(source, T, new_len, dims...)
18+
@inbounds fill!(
19+
(@view output[new_len, Base.OneTo.(dims)...]),
20+
zero(T)
21+
)
22+
temp = reinterpret(S, output)
23+
@inbounds (
24+
@view temp[Base.OneTo(len), Base.OneTo.(dims)...]
25+
) .= source
26+
end
27+
return output
28+
29+
end
30+
#=============================================================================#

src/QuantumClifford.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}
482482
483483
See also: [`stabilizerview`](@ref), [`destabilizerview`](@ref), [`logicalxview`](@ref), [`logicalzview`](@ref)
484484
"""
485-
tab(s::Stabilizer{T}) where {T} = s.tab
486485
tab(s::AbstractStabilizer) = s.tab
486+
tab(t::Tableau) = t
487487

488488
##############################
489489
# Destabilizer formalism

src/randoms.jl

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,29 @@ using Random: randperm, AbstractRNG, GLOBAL_RNG, rand!
22
using ILog2
33
import Nemo
44

5+
# Modified from `Base` for `BitArray`
6+
@inline _msk_end(::Type{T}, l::Int) where {T<:Unsigned} = ~T(0) >>> _mod(T, -l)
7+
8+
# A mask for the non-coding bits in the last chunk.
9+
@inline _msk_end(P::PauliOperator) = _msk_end(eltype(P.xz), length(P))
10+
@inline _msk_end(tab::Tableau) = _msk_end(eltype(tab.xzs), size(tab, 2))
11+
12+
# Unset the leftover bits in the data array that don't code the Pauli operator.
13+
@inline function _unset_noncoding_bits!(P::PauliOperator)
14+
msk = _msk_end(P)
15+
P.xz[end] &= msk
16+
P.xz[end÷2] &= msk
17+
nothing
18+
end
19+
@inline function _unset_noncoding_bits!(tab::Tableau)
20+
msk = _msk_end(tab)
21+
for i in eachindex(tab)
22+
@inbounds tab.xzs[end, i] &= msk
23+
@inbounds tab.xzs[end÷2, i] &= msk
24+
end
25+
nothing
26+
end
27+
528
##############################
629
# Random Paulis
730
##############################
@@ -21,21 +44,6 @@ function random_pauli end
2144
"""An in-place version of [`random_pauli`](@ref)"""
2245
function random_pauli! end
2346

24-
25-
# Modified from `Base` for `BitArray`
26-
@inline _msk_end(::Type{T}, l::Int) where {T<:Unsigned} = ~T(0) >>> _mod(T, -l)
27-
28-
# A mask for the non-coding bits in the last chunk.
29-
@inline _msk_end(P::PauliOperator) = _msk_end(eltype(P.xz), length(P))
30-
31-
# Unset the leftover bits in the data array that don't code the Pauli operator.
32-
@inline function _unset_noncoding_bits!(P::PauliOperator)
33-
msk = _msk_end(P)
34-
P.xz[end] &= msk
35-
P.xz[end÷2] &= msk
36-
nothing
37-
end
38-
3947
function random_pauli!(rng::AbstractRNG, P::PauliOperator; nophase=true, realphase=true)
4048
rand!(rng, P.xz)
4149
_unset_noncoding_bits!(P)
@@ -60,6 +68,68 @@ random_pauli(n::Int; kwargs...) = random_pauli(GLOBAL_RNG, n; kwargs...)
6068
random_pauli(rng::AbstractRNG,n::Int,p; kwargs...) = random_pauli!(rng, zero(PauliOperator, n),p; kwargs...)
6169
random_pauli(n::Int, p; kwargs...) = random_pauli(GLOBAL_RNG,n,p; kwargs...)
6270

71+
##############################
72+
# Random Tableaux
73+
##############################
74+
75+
"""A random tableau with r rows on n qubits, suitable for bulk sampling.
76+
77+
Each row is equivalent to an instance of [`random_pauli`](@ref) without any
78+
additional commutativity constraints. For proper states, consider utilising
79+
[`random_stabilizer`](@ref) or [`random_destabilizer`](@ref) as necessary.
80+
81+
Use `nophase=false` to randomize the phase.
82+
Use `realphase=false` to get operators with phases including ±i.
83+
84+
85+
Optionally, a "flip" probability `p` can be provided specified,
86+
in which case each bit is set to I with probability `1-p` and to
87+
X or Y or Z with probability `p`. Useful for simulating unbiased Pauli noise.
88+
89+
See also [`random_tableau!`](@ref)"""
90+
function random_tableau end
91+
"""An in-place version of [`random_tableau`](@ref)"""
92+
function random_tableau! end
93+
94+
function random_tableau!(rng::AbstractRNG, tab::Tableau; nophase=true, realphase=true)
95+
rand!(rng, tab.xzs)
96+
_unset_noncoding_bits!(tab)
97+
if nophase
98+
fill!(tab.phases, 0x0)
99+
elseif realphase
100+
rand!(rng, tab.phases, (0x0, 0x2))
101+
else
102+
rand!(rng, tab.phases, 0x0 : 0x3)
103+
end
104+
tab
105+
end
106+
random_tableau!(tab::Tableau; kwargs...) = random_tableau!(GLOBAL_RNG, tab; kwargs...)
107+
function random_tableau!(rng::AbstractRNG, tab::Tableau, p; nophase=true, realphase=true)
108+
n = nqubits(tab)
109+
p = p/3
110+
for i in eachindex(tab)
111+
current_pauli = tab[i]
112+
for q in 1:n
113+
r = rand(rng)
114+
@inbounds current_pauli[q] = (r<=2p), (p<r<=3p)
115+
end
116+
end
117+
if nophase
118+
fill!(tab.phases, 0x0)
119+
elseif realphase
120+
rand!(rng, tab.phases, (0x0, 0x2))
121+
else
122+
rand!(rng, tab.phases, 0x0 : 0x3)
123+
end
124+
tab
125+
end
126+
random_tableau!(tab::Tableau, p; kwargs...) = random_tableau!(GLOBAL_RNG, tab, p; kwargs...)
127+
128+
random_tableau(rng::AbstractRNG, r::Int, n::Int; kwargs...) = random_tableau!(rng, zero(Tableau, r, n); kwargs...)
129+
random_tableau(r::Int, n::Int; kwargs...) = random_tableau(GLOBAL_RNG, r, n; kwargs...)
130+
random_tableau(rng::AbstractRNG, r::Int, n::Int, p; kwargs...) = random_tableau!(rng, zero(Tableau, r, n), p; kwargs...)
131+
random_tableau(r::Int, n::Int, p; kwargs...) = random_tableau(GLOBAL_RNG, r, n, p; kwargs...)
132+
63133
##############################
64134
# Random Binary Matrices
65135
##############################

test/runtests.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ CUDA_flag && Pkg.add("CUDA")
3131
ROCm_flag && Pkg.add("AMDGPU")
3232
OpenCL_flag && Pkg.add(["pocl_jll", "OpenCL"])
3333
if any((CUDA_flag, ROCm_flag, OpenCL_flag))
34-
Pkg.add(["Atomix", "GPUArraysCore", "GPUArrays", "KernelAbstractions"])
34+
Pkg.add(
35+
["Adapt", "Atomix", "GPUArraysCore", "GPUArrays", "KernelAbstractions"]
36+
)
3537
end
3638
Oscar_flag && Pkg.add("Oscar")
3739
using TestItemRunner

0 commit comments

Comments
 (0)