@@ -171,8 +171,199 @@ random_destabilizer(n::Int; phases::Bool=true) = random_destabilizer(GLOBAL_RNG,
171171random_destabilizer (rng:: AbstractRNG , r:: Int , n:: Int ; phases:: Bool = true ) = MixedDestabilizer (random_destabilizer (rng,n;phases),r)
172172random_destabilizer (r:: Int , n:: Int ; phases:: Bool = true ) = random_destabilizer (GLOBAL_RNG,r,n; phases)
173173
174+ # Reuse memory for faster generation of many random destabilizers
175+ struct RandDestabMemory{N,T<: Integer }
176+ F1:: Matrix{Int8}
177+ F2:: Matrix{Int8}
178+ hadamard:: BitVector
179+ perm:: Vector{T}
180+ had_idxs:: Vector{T}
181+ perm_inds:: Vector{T}
182+ U:: Matrix{Int8}
183+ xzs:: BitMatrix
184+ phasesarray:: Vector{UInt8}
185+ phase_options:: Vector{UInt8}
186+ arr:: Vector{T}
187+ n:: Int64
188+
189+ function RandDestabMemory (n:: Integer )
190+ T = typeof (n)
191+ F1 = zeros (Int8, 2 n, 2 n)
192+ F2 = zeros (Int8, 2 n, 2 n)
193+ hadamard = falses (n)
194+ perm = zeros (T, n)
195+ had_idxs = zeros (T, n)
196+ perm_inds = zeros (T, 2 n)
197+ U = zeros (Int8, 2 n, 2 n)
198+ xzs = falses (2 n, 2 n)
199+ phasesarray = zeros (UInt8, 2 n)
200+ phase_options = [0x0 , 0x2 ]
201+ arr = collect (1 : n)
202+ new {n,T} (F1, F2, hadamard, perm, had_idxs, perm_inds, U, xzs, phasesarray, phase_options, arr, n)
203+ end
204+ end
205+ function _reset! (memory:: RandDestabMemory )
206+ fill! (memory. F1, 0 )
207+ fill! (memory. F2, 0 )
208+ fill! (memory. hadamard, false )
209+ fill! (memory. perm, 0 )
210+ fill! (memory. had_idxs, 0 )
211+ fill! (memory. perm_inds, 0 )
212+ fill! (memory. U, 0 )
213+ fill! (memory. xzs, false )
214+ fill! (memory. phasesarray, 0 )
215+ for i in eachindex (memory. arr)
216+ @inbounds memory. arr[i] = i
217+ end
218+ end
219+
220+ # Allocation free inverse of upper trinagular int matrix with 1 on diagonal.
221+ function _inv! (inverse, A)
222+ for i in 2 : size (A, 2 )
223+ for j in 1 : i- 1
224+ @inbounds factor = A[j, i] # diagonal always 1 => only integers in inverse matrix
225+ for k in 1 : j
226+ @inbounds inverse[k, i] -= factor * inverse[k, j]
227+ end
228+ end
229+ end
230+ nothing
231+ end
232+
233+ function random_destabilizer (rng:: AbstractRNG , memory:: RandDestabMemory ; phases:: Bool = true )
234+ n = memory. n
235+ # reset the working memory
236+ _reset! (memory)
237+
238+ hadamard = memory. hadamard
239+ perm = memory. perm
240+ _quantum_mallows! (rng, hadamard, perm, memory. arr)
241+ had_idxs = memory. had_idxs
242+ j = 1
243+ for i in eachindex (hadamard)
244+ @inbounds if hadamard[i]
245+ @inbounds had_idxs[j] = i
246+ j += 1
247+ end
248+ end
249+
250+ # delta, delta', gamma, gamma' appear in the canonical form
251+ # of a Clifford operator (Eq. 3/Theorem 1)
252+ # delta is unit lower triangular, gamma is symmetric
253+ F1 = memory. F1
254+ F2 = memory. F2
255+ delta = @view F1[1 : n, 1 : n]
256+ delta_p = @view F2[1 : n, 1 : n]
257+ prod = @view F1[n+ 1 : 2 n, 1 : n]
258+ prod_p = @view F2[n+ 1 : 2 n, 1 : n]
259+ gamma = @view F1[1 : n, n+ 1 : 2 n]
260+ gamma_p = @view F2[1 : n, n+ 1 : 2 n]
261+ inv_delta = @view F1[n+ 1 : 2 n, n+ 1 : 2 n]
262+ inv_delta_p = @view F2[n+ 1 : 2 n, n+ 1 : 2 n]
263+ for i in 1 : n
264+ delta[i, i] = 1
265+ delta_p[i, i] = 1
266+ gamma_p[i, i] = rand (rng, 0x0 : 0x1 ):: UInt8
267+ end
268+
269+ # gamma_ii is zero if h[i] = 0
270+ for idx in had_idxs
271+ if idx != 0
272+ gamma[idx, idx] = rand (rng, 0x0 : 0x1 ):: UInt8
273+ end
274+ end
275+
276+ # gamma' and delta' are unconstrained on the lower triangular
277+ fill_tril (rng, gamma_p, n, symmetric= true ) # I think this should be fill_tril!
278+ fill_tril (rng, delta_p, n)
279+
280+ # off diagonal: gamma, delta must obey conditions C1-C5
281+ for row in 1 : n, col in 1 : row- 1
282+ if hadamard[row] && hadamard[col]
283+ gamma[row, col] = gamma[col, row] = rand (rng, 0x0 : 0x1 ):: UInt8
284+ # otherwise delta[row,col] must be zero by C4
285+ if perm[row] > perm[col]
286+ delta[row, col] = rand (rng, 0x0 : 0x1 ):: UInt8
287+ end
288+ elseif hadamard[row] && (! hadamard[col]) && perm[row] < perm[col]
289+ # C5 imposes delta[row, col] = 0 for h[row]=1, h[col]=0
290+ # if perm[row] > perm[col] then C2 imposes gamma[row,col] = 0
291+ gamma[row, col] = gamma[col, row] = rand (rng, 0x0 : 0x1 ):: UInt8
292+ elseif (! hadamard[row]) && hadamard[col]
293+ delta[row, col] = rand (rng, 0x0 : 0x1 ):: UInt8
294+ # not sure what condition imposes this
295+ if perm[row] > perm[col]
296+ gamma[row, col] = gamma[col, row] = rand (rng, 0x0 : 0x1 ):: UInt8
297+ end
298+ elseif (! hadamard[row]) && (! hadamard[col]) && perm[row] < perm[col]
299+ # C1 imposes gamma[row, col] = 0 for h[row]=h[col] = 0
300+ # if perm[row] > perm[col] then C3 imposes delta[row,col] = 0
301+ delta[row, col] = rand (rng, 0x0 : 0x1 ):: UInt8
302+ end
303+ end
304+
305+ # now construct the tableau representation for F(I, Gamma, Delta)
306+ mul! (prod, gamma, delta)
307+ mul! (prod_p, gamma_p, delta_p)
308+ for i in n+ 1 : 2 n
309+ @inbounds F1[i, i] = 1
310+ @inbounds F2[i, i] = 1
311+ end
312+ _inv! (inv_delta, delta' )
313+ _inv! (inv_delta_p, delta_p' )
314+
315+ # block matrix form
316+ F1 .= mod .(F1, 2 )
317+ F2 .= mod .(F2, 2 )
318+ gamma .= 0
319+ gamma_p .= 0
320+
321+ # apply qubit permutation S to F2
322+ perm_inds = memory. perm_inds
323+ U = memory. U
324+ for i in 1 : n
325+ @inbounds perm_inds[i] = perm[i]
326+ @inbounds perm_inds[i+ n] = perm[i] + n
327+ end
328+ for (i, e) in enumerate (perm_inds)
329+ for j in 1 : 2 n
330+ U[i, j] = F2[e, j]
331+ end
332+ end
333+
334+ # apply layer of hadamards
335+ for i in 1 : n
336+ if had_idxs[i] != 0
337+ for j in 1 : 2 n
338+ @inbounds (U[had_idxs[i], j], U[had_idxs[i]+ n, j]) = (U[had_idxs[i]+ n, j], U[had_idxs[i], j])
339+ end
340+ end
341+ end
342+
343+ # apply F1
344+ xzs = memory. xzs
345+ fill! (F2, 0 )
346+ mul! (F2, F1, U)
347+
348+ F2 .= mod .(F2, 2 )
349+ xzs .= F2 .== 1
350+
351+ # random Pauli matrix just amounts to phases on the stabilizer tableau
352+ phasesarray = memory. phasesarray
353+ if phases
354+ for i in 1 : 2 n
355+ phasesarray[i] = rand (rng, memory. phase_options)
356+ end
357+ end
358+
359+ return Destabilizer (Tableau (phasesarray, xzs))
360+ end
361+
362+
363+
174364""" A random Clifford operator generated by the Bravyi-Maslov Algorithm 2 from [bravyi2020hadamard](@cite)."""
175365random_clifford (rng:: AbstractRNG , n:: Int ; phases:: Bool = true ) = CliffordOperator (random_destabilizer (rng, n; phases))
366+ random_clifford (rng:: AbstractRNG , memory:: RandDestabMemory ; phases:: Bool = true ) = CliffordOperator (random_destabilizer (rng, memory; phases))
176367random_clifford (n:: Int ; phases:: Bool = true ) = random_clifford (GLOBAL_RNG, n:: Int ; phases)
177368
178369""" A random Stabilizer tableau generated by the Bravyi-Maslov Algorithm 2 from [bravyi2020hadamard](@cite)."""
@@ -196,23 +387,49 @@ function nemo_inv(a, n)::Matrix{UInt8}
196387 return collect (UInt8 .(inverted.== 1 )) # maybe there is a better way to do the conversion
197388end
198389
390+
199391""" Sample (h, S) from the distribution P_n(h, S) from Bravyi and Maslov Algorithm 1."""
200392function quantum_mallows (rng:: AbstractRNG , n:: Int ) # each one is benchmarked in benchmarks/quantum_mallows.jl
201393 arr = collect (1 : n)
202394 hadamard = falses (n)
203395 perm = zeros (Int64, n)
396+
397+ return _quantum_mallows! (rng, hadamard, perm, arr)
398+ end
399+
400+ function _quantum_mallows! (
401+ rng:: AbstractRNG ,
402+ hadamard:: BitVector ,
403+ perm:: Vector{T} ,
404+ arr:: Vector{T} ) where {T<: Integer } # inplace version
405+
406+ n = length (perm)
204407 for idx in 1 : n
205- m = length (arr)
408+ m = n - idx + 1
206409 # sample h_i from given prob distribution
207410 l = sample_geometric_2 (rng, 2 * m)
208411 weight = 2 * m - l
209412 hadamard[idx] = (weight < m)
210- k = weight < m ? weight : 2 * m - weight - 1
211- perm[idx] = popat ! (arr, k + 1 )
413+ k = weight < m ? weight : 2 * m - weight - 1
414+ perm[idx] = _popat ! (arr, k + 1 )
212415 end
213416 return hadamard, perm
214417end
215418
419+ function _popat! (arr, n)
420+ m = length (arr)
421+ at = arr[n]
422+
423+ for i in n: (m- 1 )
424+ arr[i] = arr[i+ 1 ]
425+ end
426+
427+ arr[end ] = 0
428+
429+ return at
430+ end
431+
432+
216433""" This function samples a number from 1 to `n` where `n >= 1`
217434 probability of outputting `i` is proportional to `2^i`"""
218435function sample_geometric_2 (rng:: AbstractRNG , n:: Integer )
@@ -263,6 +480,7 @@ function random_brickwork_clifford_circuit(rng::AbstractRNG, lattice_size::NTupl
263480 cartesian = CartesianIndices (lattice_size)
264481 dim = length (lattice_size)
265482 nqubits = prod (lattice_size)
483+ working_memory = RandDestabMemory (2 )
266484 for i in 1 : nlayers
267485 gate_direction = (i - 1 ) % dim + 1
268486 l = lattice_size[gate_direction]
@@ -273,7 +491,7 @@ function random_brickwork_clifford_circuit(rng::AbstractRNG, lattice_size::NTupl
273491 cardk = cardj
274492 cardk[gate_direction] = cardk[gate_direction] + 1
275493 k = LinearIndices (cartesian)[cardk... ]
276- push! (circ, SparseGate (random_clifford (rng, 2 ), [j, k]))
494+ push! (circ, SparseGate (random_clifford (rng, working_memory ), [j, k]))
277495 end
278496 end
279497 end
0 commit comments