Skip to content

Commit 749d377

Browse files
authored
support compactifications of more sophisticated types (#264)
Now compactification works for more types. In particular see these examples of things that previously could not be compactified: using Revise using QuantumClifford using BenchmarkTools function x_diag_circuit_noisy_measurement(csize) circuit = [] for i in 1:csize push!(circuit, PauliError(i, 0.1)) push!(circuit, sHadamard(i)) push!(circuit, sCNOT(i, csize+1)) push!(circuit, sMZ(csize+1,i)) push!(circuit, ClassicalXOR(1:(i%6+2),i)) end return circuit end @benchmark pftrajectories(state,circuit) setup=(state=PauliFrame(1000, 1001, 1001); circuit=x_diag_circuit_noisy_measurement(1000)) evals=1 BenchmarkTools.Trial: 1126 samples with 1 evaluation. Range (min … max): 3.532 ms … 4.088 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 3.573 ms ┊ GC (median): 0.00% Time (mean ± σ): 3.577 ms ± 27.682 μs ┊ GC (mean ± σ): 0.00% ± 0.00% ▁▆▄▅▇▄▇█▆▆▃▆▄▅▅▄▂▁▂▁ ▃▂▁▃▃▁▂▄▄▄▄▇█████████████████████▇▇▆▆▆▆▄▆▃▃▅▅▄▄▃▂▃▃▂▃▂▂▃▂▃ ▅ 3.53 ms Histogram: frequency by time 3.63 ms < Memory estimate: 281.25 KiB, allocs estimate: 6000. @benchmark pftrajectories(state,circuit) setup=(state=PauliFrame(1000, 1001, 1001); circuit=compactify_circuit(x_diag_circuit_noisy_measurement(1000))) evals=1 Before: BenchmarkTools.Trial: 53 samples with 1 evaluation. Range (min … max): 3.495 ms … 5.890 ms ┊ GC (min … max): 0.00% … 38.90% Time (median): 3.623 ms ┊ GC (median): 0.00% Time (mean ± σ): 3.711 ms ± 370.663 μs ┊ GC (mean ± σ): 1.16% ± 5.34% █▃▂ ▂▂ ▄▄████▇▇██▄▇▇▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▁▁▁▄ ▁ 3.5 ms Histogram: frequency by time 4.71 ms < Memory estimate: 421.97 KiB, allocs estimate: 9002. After: BenchmarkTools.Trial: 1116 samples with 1 evaluation. Range (min … max): 3.221 ms … 4.887 ms ┊ GC (min … max): 0.00% … 28.58% Time (median): 3.388 ms ┊ GC (median): 0.00% Time (mean ± σ): 3.357 ms ± 150.766 μs ┊ GC (mean ± σ): 0.36% ± 2.70% ▄█▆▂ ▂▂▃▆▆▇▇▅▇▆▆▅▄▄▄▄▃▃▃▃▂▂▃▃▂▂▁▂▂▂▁▁▁▃▃▅█████▇▆▃▄▂▂▂▂▁▁▂▃▃▃▃▁▁▂ ▃ 3.22 ms Histogram: frequency by time 3.49 ms < Memory estimate: 187.50 KiB, allocs estimate: 4000.
1 parent 3cdf46b commit 749d377

File tree

7 files changed

+142
-55
lines changed

7 files changed

+142
-55
lines changed

CHANGELOG.md

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

1010
- Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module.
1111
- Remove printing of spurious debug info from the PyBP decoder.
12+
- Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits.
1213

1314
## v0.9.3 - 2024-04-10
1415

benchmark/benchmarks.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,25 @@ for (cs, c) in [("shor",Shor9()), ("toric8",Toric(8,8))]
158158
end
159159
end
160160

161+
162+
if V > v"0.9.0"
163+
164+
function x_diag_circuit_noisy_measurement(csize)
165+
circuit = []
166+
for i in 1:csize
167+
push!(circuit, PauliError(i, 0.1))
168+
push!(circuit, sHadamard(i))
169+
push!(circuit, sCNOT(i, csize+1))
170+
push!(circuit, sMZ(csize+1,i))
171+
push!(circuit, ClassicalXOR(1:(i%6+2),i))
172+
end
173+
return circuit
174+
end
175+
176+
SUITE["circuitsim"]["compactification"] = BenchmarkGroup(["compactification"])
177+
SUITE["circuitsim"]["compactification"]["no_compact"] = @benchmarkable pftrajectories(state,circuit) setup=(state=PauliFrame(1000, 1001, 1001); circuit=x_diag_circuit_noisy_measurement(1000)) evals=1
178+
SUITE["circuitsim"]["compactification"]["compact"] = @benchmarkable pftrajectories(state,circuit) setup=(state=PauliFrame(1000, 1001, 1001); circuit=compactify_circuit(x_diag_circuit_noisy_measurement(1000))) evals=1
179+
180+
end
181+
161182
end

ext/QuantumCliffordQuantikzExt/QuantumCliffordQuantikzExt.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Quantikz
44
using QuantumClifford
55
using QuantumClifford.Experimental.NoisyCircuits
66
using QuantumClifford: AbstractOperation
7-
using QuantumClifford: ClassicalXORConcreteWorkaround
87

98
function Quantikz.QuantikzOp(op::SparseGate)
109
g = op.cliff
@@ -73,7 +72,7 @@ function Quantikz.QuantikzOp(op::Reset) # TODO This is complicated because quant
7372
end
7473
Quantikz.QuantikzOp(op::NoiseOp) = Quantikz.Noise(collect(op.indices))
7574
Quantikz.QuantikzOp(op::NoiseOpAll) = Quantikz.NoiseAll()
76-
Quantikz.QuantikzOp(op::ClassicalXORConcreteWorkaround) = Quantikz.ClassicalDecision(sort([op.store, op.bits...]))
75+
Quantikz.QuantikzOp(op::ClassicalXOR) = Quantikz.ClassicalDecision(sort([op.store, op.bits...]))
7776

7877
function lstring(pauli::PauliOperator)
7978
v = join(("\\mathtt{$(o)}" for o in replace(string(pauli)[3:end],"_"=>"I")),"\\\\")

src/affectedqubits.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ affectedqubits(p::PauliOperator) = 1:length(p)
1212
affectedqubits(m::Union{AbstractMeasurement,sMRX,sMRY,sMRZ}) = (m.qubit,)
1313
affectedqubits(v::VerifyOp) = v.indices
1414
affectedqubits(c::CliffordOperator) = 1:nqubits(c)
15-
affectedqubits(c::ClassicalXORConcreteWorkaround) = ()
15+
affectedqubits(c::ClassicalXOR) = ()
1616

1717
affectedbits(o) = ()
1818
affectedbits(m::sMRZ) = (m.bit,)
1919
affectedbits(m::sMZ) = (m.bit,)
20-
affectedbits(c::ClassicalXORConcreteWorkaround) = (c.bits..., c.store)
20+
affectedbits(c::ClassicalXOR) = (c.bits..., c.store)

src/misc_ops.jl

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -127,34 +127,15 @@ end
127127

128128
operatordeterminism(::Type{VerifyOp}) = DeterministicOperatorTrait()
129129

130-
abstract type ClassicalXORConcreteWorkaround <: AbstractOperation end # See below for more of this abomination - replace everywhere by ClassicalXOR when compactification is fixed
131130
"""Applies an XOR gate to classical bits. Currently only implemented for functionality with pauli frames."""
132-
struct ClassicalXOR{N} <: ClassicalXORConcreteWorkaround
131+
struct ClassicalXOR{N} <: AbstractOperation
133132
"The indices of the classical bits to be xor-ed"
134133
bits::NTuple{N,Int}
135134
"The index of the classical bit that will store the results"
136135
store::Int
137-
function ClassicalXOR(bits, store) # See below for more of this abomination
136+
function ClassicalXOR(bits, store)
138137
tbits = tuple(bits...)
139-
n = length(bits)
140-
if n <= 15
141-
return eval(Symbol("ClassicalXOR",string(n)))(tbits, store)
142-
else
143-
return new{n}(tbits, store)
144-
end
138+
n = length(tbits)
139+
return new{n}(tbits, store)
145140
end
146141
end
147-
#ClassicalXOR(bits::Vector, store::Int) = ClassicalXOR(tuple(bits...),store)
148-
# XXX TODO remove this abomination
149-
# Workaround for not being able to compactify non-concrete types
150-
for n in 2:15
151-
name = Symbol("ClassicalXOR",string(n))
152-
eval(
153-
quote
154-
struct $name <: ClassicalXORConcreteWorkaround
155-
bits::NTuple{$n,Int}
156-
store::Int
157-
end
158-
end
159-
)
160-
end

src/pauli_frames.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function apply!(f::PauliFrame, op::AbstractCliffordOperator)
5757
return f
5858
end
5959

60-
function apply!(frame::PauliFrame, xor::ClassicalXORConcreteWorkaround)
60+
function apply!(frame::PauliFrame, xor::ClassicalXOR)
6161
for f in eachindex(frame)
6262
value = frame.measurements[f,xor.bits[1]]
6363
for i in xor.bits[2:end]

src/sumtypes.jl

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1+
# Here be dragons...
2+
13
using SumTypes
24
using InteractiveUtils: subtypes
35

6+
"""An intermediary when we want to create a new concrete type in a macro."""
7+
struct SymbolicDataType
8+
name::Symbol
9+
types#::Core.SimpleVector
10+
fieldnames
11+
originaltype
12+
end
13+
_header(s) = s
14+
_header(s::SymbolicDataType) = s.name
15+
_symbol(s) = Symbol(s)
16+
_symbol(s::SymbolicDataType) = s.name
17+
_types(s) = s.types
18+
_fieldnames(s) = fieldnames(s)
19+
_fieldnames(s::SymbolicDataType) = s.fieldnames
20+
_originaltype(s) = s
21+
_originaltype(s::SymbolicDataType) = s.originaltype
22+
423
"""
524
```
625
julia> make_variant(sCNOT)
726
:(sCNOT(::Int64, ::Int64))
827
```
928
"""
10-
function make_variant(type::DataType)
11-
Expr(:call, Symbol(type), [:(::$t) for t in type.types]...)
29+
function make_variant(type::Union{DataType,SymbolicDataType})
30+
Expr(:call, _symbol(type), [:(::$t) for t in _types(type)]...)
1231
end
1332

1433
"""
@@ -17,9 +36,9 @@ julia> make_variant_deconstruct(sCNOT, :apply!, (:s,))
1736
:(sCNOT(q1, q2) => apply!(s, sCNOT(q1, q2)))
1837
```
1938
"""
20-
function make_variant_deconstruct(type::DataType, call, preargs=(), postargs=())
21-
variant = Expr(:call, Symbol(type), fieldnames(type)...)
22-
original = :(($type)($(fieldnames(type)...)))
39+
function make_variant_deconstruct(type::Union{DataType,SymbolicDataType}, call, preargs=(), postargs=())
40+
variant = Expr(:call, _symbol(type), _fieldnames(type)...)
41+
original = :(($(_originaltype(type)))($(_fieldnames(type)...)))
2342
:($variant => $(Expr(:call, call, preargs..., original, postargs...)))
2443
end
2544

@@ -36,7 +55,7 @@ end
3655
function make_sumtype(concrete_types)
3756
return quote
3857
@sum_type CompactifiedGate :hidden begin
39-
$([make_variant(t) for t in concrete_types if isa(t, DataType)]...)
58+
$([make_variant(t) for t in concrete_types if isa(t, DataType) || isa(t, SymbolicDataType)]...)
4059
end
4160
end
4261
end
@@ -56,7 +75,8 @@ function make_sumtype_method(concrete_types, call, preargs=(), postargs=())
5675
return quote
5776
function QuantumClifford.$call($(preargs...), g::CompactifiedGate, $(postargs...))
5877
@cases g begin
59-
$([make_variant_deconstruct(t, call, preargs, postargs) for t in concrete_types if isa(t, DataType)]...)
78+
$([make_variant_deconstruct(t, call, preargs, postargs) for t in concrete_types if isa(t, DataType) || isa(t, SymbolicDataType)]...)
79+
#_ => @error "something wrong is happening when working with $(g) -- you are probably getting wrong results, please report this as a bug" # this being present ruins some safety guarantees, but it is useful for debugging
6080
end
6181
end
6282
end
@@ -71,40 +91,89 @@ end)
7191
```
7292
"""
7393
function make_sumtype_variant_constructor(type)
74-
if isa(type, DataType)
75-
return :( CompactifiedGate(g::$(type)) = CompactifiedGate'.$(Symbol(type))($([:(g.$n) for n in fieldnames(type)]...)) )
94+
if isa(type, DataType) || isa(type, SymbolicDataType)
95+
return :( CompactifiedGate(g::$(_header(type))) = CompactifiedGate'.$(_symbol(type))($([:(g.$n) for n in _fieldnames(type)]...)) )
7696
else
77-
return :( CompactifiedGate(g::$(type)) = (@warn "The operation is of a type that can not be unified, defaulting to slower runtime dispatch" typeof(g); return g) )
97+
#return :( CompactifiedGate(g::$(_header(type))) = (@warn "The operation is of a type that can not be unified, defaulting to slower runtime dispatch" typeof(g); return g) )
98+
return :()
7899
end
79100
end
80101

102+
genericsupertypeparams(t) = :body propertynames(t) ? genericsupertypeparams(t.body) : t
103+
104+
"""Returns a tuple of all concrete subtypes and all UnionAll non-abstract subtypes of a given type."""
105+
function get_all_subtypes(type)
106+
if !isabstracttype(type)
107+
if isa(type, DataType)
108+
isbitstype(type) || @debug "$type will be problematic during compactification"
109+
return [type], []
110+
elseif isa(type, UnionAll)
111+
return [], [type]
112+
else
113+
@error "The gate compiler has encountered a type that it can not handle: $type. The QuantumClifford library should continue functioning, but potentially at degraded performance. Please report this as a performance bug."
114+
end
115+
else
116+
return Iterators.flatten.(zip(get_all_subtypes.(subtypes(type))...))
117+
end
118+
end
81119

82-
function make_all_sumtype_infrastructure_expr(concrete_types, callsigs)
120+
module_of_type(t::UnionAll) = genericsupertypeparams(t).name.module
121+
module_of_type(t::DataType) = t.name.module
122+
123+
function make_all_sumtype_infrastructure_expr(t::DataType, callsigs)
124+
concrete_types, unionall_types = get_all_subtypes(t)
125+
concrete_types = collect(Any, concrete_types)
126+
concrete_types = Any[t for t in concrete_types if module_of_type(t)==QuantumClifford]
127+
unionall_types = Any[t for t in unionall_types if module_of_type(t)==QuantumClifford]
128+
concretifier_workarounds_types = [] # e.g. var"ClassicalXOR_{2}" generated as a workaround for providing a concrete type for ClassicalXOR{N}
129+
concretifier_additional_constructors = [] # e.g. CompactifiedGate(g::ClassicalXOR{2}) = CompactifiedGate'.var"ClassicalXOR_{2}"(g.bits, g.store)
130+
for ut in unionall_types
131+
names, generated_concretetypes, generated_variant_constructors = concretifier(ut)
132+
append!(concretifier_workarounds_types, generated_concretetypes)
133+
append!(concrete_types, names)
134+
append!(concretifier_additional_constructors, generated_variant_constructors)
135+
push!(concrete_types, ut) # fallback
136+
end
83137
sumtype = make_sumtype(concrete_types)
84138
constructors = make_sumtype_variant_constructor.(concrete_types)
85139
methods = [make_sumtype_method(concrete_types, call, preargs, postargs) for (call, preargs, postargs) in callsigs]
86140
return quote
87-
$(sumtype.args...)
88-
$(constructors...)
141+
$(concretifier_workarounds_types...)
142+
$(sumtype.args...) # defining the sum type
143+
$(constructors...) # creating constructors for the sumtype which turn our concrete types into instance of the sum type
144+
$(concretifier_additional_constructors...) # creating constructors for the newly generated "workaround" concrete types
145+
:( CompactifiedGate(g::AbstractOperation) = (@warn "The operation is of a type that can not be unified, defaulting to slower runtime dispatch" typeof(g); return g) )
89146
$(methods...)
90147
end
91148
end
92149

93-
function get_all_concrete_subtypes(type)
94-
if !isabstracttype(type)
95-
return [type]
96-
else
97-
return vcat(get_all_concrete_subtypes.(subtypes(type))...)
98-
end
150+
function concrete_typeparams(t)
151+
@debug "The gate compiler is not able to concretify the type $t. Define a `concrete_typeparams` method for this type to improve performance."
152+
return ()
99153
end
100154

101-
module_of_type(t::UnionAll) = module_of_type(t.body)
102-
module_of_type(t::DataType) = t.name.module
155+
function concretifier(t)
156+
names = []
157+
generated_concretetypes = []
158+
generated_variant_constructors = []
159+
for typeparams in concrete_typeparams(t)
160+
name = Symbol(t,"{",typeparams,"}")
161+
parameterized_type = t{typeparams...}
162+
ftypes = parameterized_type.types
163+
fnames = fieldnames(t)
164+
push!(names, SymbolicDataType(name, ftypes, fnames, t))
165+
push!(generated_concretetypes, :(
166+
struct $(name)
167+
$([:($n::$t) for (n,t) in zip(fnames,ftypes)]...)
168+
end
169+
))
170+
push!(generated_variant_constructors, make_concretifier_sumtype_variant_constructor(parameterized_type, name))
171+
end
172+
return names, generated_concretetypes, generated_variant_constructors
173+
end
103174

104-
function make_all_sumtype_infrastructure_expr(t::DataType, callsigs)
105-
concrete_types = get_all_concrete_subtypes(t)
106-
non_experimental_concrete_types = [t for t in concrete_types if module_of_type(t)==QuantumClifford]
107-
make_all_sumtype_infrastructure_expr(non_experimental_concrete_types, callsigs)
175+
function make_concretifier_sumtype_variant_constructor(parameterized_type, variant_name)
176+
return :( CompactifiedGate(g::$(parameterized_type)) = CompactifiedGate'.$(variant_name)($([:(g.$n) for n in _fieldnames(parameterized_type)]...)) )
108177
end
109178

110179
function make_all_sumtype_infrastructure()
@@ -120,8 +189,6 @@ function make_all_sumtype_infrastructure()
120189
) |> eval
121190
end
122191

123-
make_all_sumtype_infrastructure()
124-
125192
"""
126193
Convert a list of gates to a more optimized "sum type" format which permits faster dispatch.
127194
@@ -130,3 +197,21 @@ Generally, this should be called on a circuit before it is used in a simulation.
130197
function compactify_circuit(circuit)
131198
return CompactifiedGate.(circuit)
132199
end
200+
201+
202+
##
203+
# `concrete_typeparams` annotations for the parameteric types we care about
204+
##
205+
206+
function concrete_typeparams(t::Type{ClassicalXOR})
207+
return 2:16
208+
end
209+
210+
function concrete_typeparams(t::Type{NoiseOp})
211+
return [(UnbiasedUncorrelatedNoise{Float64}, i) for i in 1:8]
212+
end
213+
214+
215+
# XXX This has to happen after defining all the `concrete_typeparams` methods
216+
217+
make_all_sumtype_infrastructure()

0 commit comments

Comments
 (0)