Simulating quantum circuits with Yao

Hi there! I’m new in the Julia ecosystems and my first project is trying to simulate a particular random circuit using Yao. My issue at the moment is basically the efficiency of code which seems to be quite slow. I would really appreciate any suggestion/advice on how to improve the code.

function circuit_layer(n::Int, β::Array{Float64, 1}, epsilon::Float64)
    U = chain(n)
    append!(U, chain(n, put(n, loc=>chain(rand(rng,randG)((2*rand())*epsilon*π))) for loc = 1:n))
    for j = 1:n-1
        append!(U, chain(n, subroutine(n, rot(ZZ, β[j]), (j, j+1))))
    end
    return U
end

function get_entropy_evolution(pmax::Int, spacing::Float64, n::Int, nA::Int, epsilon::Float64)
    fp = trunc(log2(pmax)) 
    times = union(trunc.([2^i for i in 1:spacing:fp]))
    ψ = Yao.uniform_state(n)
    result = zeros(length(times))
    count = 0
    for t in 1:pmax
        β = (π*0.5)*(2.0*rand(n-1) .- 1.0)
        ψ |> circuit_layer(n, β, epsilon)
        if (t in times) == true
            count = count + 1
            result[count] = entropy(state(ψ), n, nA, 1)
        end
    end
    result
end

The code works as follows: 1.) Construct one layer of the circuit, that is captured by the function circuit_layer, 2.) Then “time evolve” the state and only compute entropy at certain points in time.

I know the function svdvals is costly but in principle, it shouldn’t be a problem for system sizes around N=20

1 Like

Hi @rmedinar do you mind provide a runnable script? your implementation gives

julia> circuit_layer(10, rand(9), 0.2)
ERROR: UndefVarError: rng not defined
Stacktrace:
 [1] (::var"#1#2"{Int64, Float64})(loc::Int64)
   @ Main ./none:0
 [2] iterate
   @ ./generator.jl:47 [inlined]
 [3] isempty
   @ ./essentials.jl:767 [inlined]
 [4] chain
   @ ~/.julia/packages/YaoBlocks/TIlDJ/src/composite/chain.jl:41 [inlined]
 [5] circuit_layer(n::Int64, β::Vector{Float64}, epsilon::Float64)
   @ Main ./REPL[2]:3
 [6] top-level scope
   @ REPL[4]:1

and I’m not sure what is randG

Thanks @Roger-luo for the quick reply. I’m really sorry, this is the first time I ask a question like this one and I forgot to add the whole runnable script. Here it goes:

using MKL, Yao, Random, YaoPlots,  LinearAlgebra, Base.Threads

const SINGLE_GATES = [Rx, Ry, Rz]
@const_gate ZZ = mat(kron(Z,Z))
rng = MersenneTwister()
randG = Random.Sampler(rng, SINGLE_GATES)

function entropy(psi::Array, n::Int, nA::Int, nrenyi::Int)
    state = svdvals(reshape(psi, (2^nA, 2^(n-nA)))) .^ 2
    if nrenyi==1
        return -sum( state.*log.(state) )
	else
		return 1.0/(1.0-nrenyi) * log( sum( state.^nrenyi))
    end
end

function circuit_layer(n::Int, β::Array{Float64, 1}, epsilon::Float64)
    U = chain(n)
    append!(U, chain(n, put(n, loc=>chain(rand(rng,randG)((2*rand())*epsilon*π))) for loc = 1:n))
    for j = 1:n-1
        append!(U, chain(n, subroutine(n, rot(ZZ, β[j]), (j, j+1))))
    end
    return U
end

function get_entropy_evolution(pmax::Int, spacing::Float64, n::Int, nA::Int, epsilon::Float64)
    fp = trunc(log2(pmax)) 
    times = union(trunc.([2^i for i in 1:spacing:fp]))
    ψ = Yao.uniform_state(n)
    result = zeros(length(times))
    count = 0
    for t in 1:pmax
        β = (π*0.5)*(2.0*rand(n-1) .- 1.0)
        ψ |> circuit_layer(n, β, epsilon)
        if (t in times) == true
            count = count + 1
            result[count] = entropy(state(ψ), n, nA, 1)
        end
    end
    result
end

julia> @time get_entropy_evolution(10, 0.1, 20, 2, 0.1);
  5.816009 seconds (36.36 k allocations: 20.596 GiB, 8.29% gc time)

If you change subroutine to put, it will be much faster

julia> function circuit_layer(n::Int, β::Array{Float64, 1}, epsilon::Float64)
           U = chain(n)
           append!(U, chain(n, put(n, loc=>chain(rand(rng,randG)((2*rand())*epsilon*π))) for loc = 1:n))
           for j = 1:n-1
               append!(U, chain(n, put(n, (j,j+1)=>rot(ZZ, β[j]))))
           end
           return U
       end
julia> @time get_entropy_evolution(10, 0.1, 20, 2, 0.1);
  0.949597 seconds (21.34 k allocations: 129.222 MiB, 0.38% gc time)

Subroutine is for computing a chunk of subprogram, it is not designed for applying a two qubit gate. This is why it is so slow.

Also, I think the time for computing SVD is comparable to computing the circuit. If you check the profile

@profile get_entropy_evolution(10, 0.1, 20, 2, 0.1)

you will see computing the circuit is even more costly than computing svdvals. This is because the svd matrix has the same length as the state vector, while you applied a lot of gates.

3 Likes

Wow, thanks! I just checked and the improvement is quite noticeable. Sure, computing svd seems comparable to computing the circuit. In principle, I’m interested in reaching large circuit depth, i.e., pmax~ O(10^3) so I think that’s the bottle neck of the computation beyond the usual Hilbert space dimension.

1 Like