Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support qudits in YaoToEinsum #530

Merged
merged 2 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/YaoBlocks/src/composite/composite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ include("tag/tag.jl")
include("tag/cache.jl")
include("tag/dagger.jl")
include("tag/scale.jl")
include("tag/onlevels.jl")
28 changes: 28 additions & 0 deletions lib/YaoBlocks/src/composite/tag/onlevels.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export OnLevels

"""
OnLevels{D, Ds, T <: AbstractBlock{Ds}} <: TagBlock{T, D}

Define a gate that is applied to a subset of levels.

### Fields
- `gate`: the gate to be applied.
- `levels`: the levels to apply the gate to.
"""
struct OnLevels{D, Ds, T <: AbstractBlock{Ds}} <: TagBlock{T, D}
gate::T
levels::NTuple{Ds, Int}
function OnLevels{D}(gate::T, levels::NTuple{Ds, Int}) where {D, Ds, T <: AbstractBlock{Ds}}
@assert nqudits(gate) == 1 "only single qubit gate is supported"
new{D, Ds, T}(gate, levels)
end
end
content(g::OnLevels) = g.gate
function mat(::Type{T}, g::OnLevels{D, Ds}) where {T, D, Ds}
m = mat(T, g.gate)
I, J, V = LuxurySparse.findnz(m)
return sparse(collect(g.levels[I]), collect(g.levels[J]), V, D, D)
end
PropertyTrait(::OnLevels) = PreserveAll()
Base.adjoint(x::OnLevels{D}) where D = OnLevels{D}(adjoint(x.gate), x.levels)
Base.copy(x::OnLevels{D}) where D = OnLevels{D}(copy(x.gate), x.levels)
4 changes: 4 additions & 0 deletions lib/YaoBlocks/src/layout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ print_annotation(io::IO, c::Daggered) =
print_annotation(io::IO, c::CachedBlock) =
printstyled(io, "[cached] "; bold = true, color = :yellow)

function print_annotation(io::IO, x::OnLevels)
printstyled(io, "[on levels: ", x.levels, "] "; bold = true, color = :yellow)
end

function print_annotation(io::IO, x::Scale)
if x.alpha == im
printstyled(io, "[+im] "; bold = true, color = :yellow)
Expand Down
19 changes: 18 additions & 1 deletion lib/YaoBlocks/test/composite/tag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,21 @@ end
@test gatecount(Val(0.1) * X) == Dict(typeof(Val(0.1)*X)=>1)
@test gatecount(cache(Val(0.1) * X)) == Dict(typeof(Val(0.1)*X)=>1)
@test gatecount(Daggered(X)) == Dict(typeof(Daggered(X))=>1)
end
end

@testset "OnLevels" begin
Z1r = OnLevels{3}(Z, (2, 3))
mz = mat(ComplexF64, Z1r)
@test YaoBlocks.SparseArrays.nnz(mz) == 2
g = OnLevels{3}(Rx(0.5), (2, 3))
g2 = copy(g)
g2.gate.theta = 0.6
@test !ishermitian(g)
@test isunitary(g)
@test g2.gate.theta == 0.6
@test g.gate.theta == 0.5
@test parameters(g) == [0.5]
@test g' == OnLevels{3}(Rx(-0.5), (2, 3))
println(g)
end

44 changes: 23 additions & 21 deletions lib/YaoToEinsum/src/circuitmap.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
struct EinBuilder{T}
# T is the element type of the tensor network
# D is the dimension of the qudits
struct EinBuilder{T, D}
slots::Vector{Int}
labels::Vector{Vector{Int}}
tensors::Vector{AbstractArray{T}}
Expand All @@ -12,29 +14,29 @@ function add_tensor!(eb::EinBuilder{T}, tensor::AbstractArray{T,N}, labels::Vect
push!(eb.labels, labels)
end

function EinBuilder(::Type{T}, n::Int) where T
EinBuilder(collect(1:n), Vector{Int}[], AbstractArray{T}[], Ref(n))
function EinBuilder{T, D}(n::Int) where {T, D}
EinBuilder{T, D}(collect(1:n), Vector{Int}[], AbstractArray{T}[], Ref(n))
end
newlabel!(eb::EinBuilder) = (eb.maxlabel[] += 1; eb.maxlabel[])

function add_gate!(eb::EinBuilder{T}, b::PutBlock{D,C}) where {T,D,C}
return add_matrix!(eb, C, mat(T, b.content), collect(b.locs))
end
# general and diagonal gates
function add_matrix!(eb::EinBuilder{T}, k::Int, m::AbstractMatrix, locs::Vector) where T
function add_matrix!(eb::EinBuilder{T, D}, k::Int, m::AbstractMatrix, locs::Vector) where {T, D}
if isdiag(m)
add_tensor!(eb, reshape(Vector{T}(diag(m)), fill(2, k)...), eb.slots[locs])
add_tensor!(eb, reshape(Vector{T}(diag(m)), fill(D, k)...), eb.slots[locs])
elseif m isa YaoBlocks.OuterProduct # low rank
nlabels = [newlabel!(eb) for _=1:k]
K = rank(m)
if K == 1 # projector
add_tensor!(eb, reshape(Vector{T}(m.right), fill(2, k)...), [eb.slots[locs]...])
add_tensor!(eb, reshape(Vector{T}(m.left), fill(2, k)...), [nlabels...])
add_tensor!(eb, reshape(Vector{T}(m.right), fill(D, k)...), [eb.slots[locs]...])
add_tensor!(eb, reshape(Vector{T}(m.left), fill(D, k)...), [nlabels...])
eb.slots[locs] .= nlabels
else
midlabel = newlabel!(eb)
add_tensor!(eb, reshape(Matrix{T}(m.right), fill(2, k)..., K), [eb.slots[locs]..., midlabel])
add_tensor!(eb, reshape(Matrix{T}(m.left), fill(2, k)..., K), [nlabels..., midlabel])
add_tensor!(eb, reshape(Matrix{T}(m.right), fill(D, k)..., K), [eb.slots[locs]..., midlabel])
add_tensor!(eb, reshape(Matrix{T}(m.left), fill(D, k)..., K), [nlabels..., midlabel])
eb.slots[locs] .= nlabels
end
else
Expand All @@ -45,31 +47,31 @@ function add_matrix!(eb::EinBuilder{T}, k::Int, m::AbstractMatrix, locs::Vector)
return eb
end
# swap gate
function add_gate!(eb::EinBuilder{T}, b::PutBlock{2,2,ConstGate.SWAPGate}) where {T}
function add_gate!(eb::EinBuilder{T, 2}, b::PutBlock{2,2,ConstGate.SWAPGate}) where {T}
lj = eb.slots[b.locs[2]]
eb.slots[b.locs[2]] = eb.slots[b.locs[1]]
eb.slots[b.locs[1]] = lj
return eb
end

# projection gate, todo: generalize to arbitrary low rank gate
function add_gate!(eb::EinBuilder{T}, b::PutBlock{2,1,ConstGate.P0Gate}) where {T}
function add_gate!(eb::EinBuilder{T, 2}, b::PutBlock{2,1,ConstGate.P0Gate}) where {T}
add_matrix!(eb, 1, YaoBlocks.OuterProduct(T[1, 0], T[1, 0]), collect(b.locs))
return eb
end

# projection gate, todo: generalize to arbitrary low rank gate
function add_gate!(eb::EinBuilder{T}, b::PutBlock{2,1,ConstGate.P1Gate}) where {T}
function add_gate!(eb::EinBuilder{T, 2}, b::PutBlock{2,1,ConstGate.P1Gate}) where {T}
add_matrix!(eb, 1, YaoBlocks.OuterProduct(T[0, 1], T[0, 1]), collect(b.locs))
return eb
end


# control gates
function add_gate!(eb::EinBuilder{T}, b::ControlBlock{BT,C,M}) where {T, BT,C,M}
function add_gate!(eb::EinBuilder{T, 2}, b::ControlBlock{BT,C,M}) where {T, BT,C,M}
return add_controlled_matrix!(eb, M, mat(T, b.content), collect(b.locs), collect(b.ctrl_locs), collect(b.ctrl_config))
end
function add_controlled_matrix!(eb::EinBuilder{T}, k::Int, m::AbstractMatrix, locs::Vector, control_locs, control_vals) where T
function add_controlled_matrix!(eb::EinBuilder{T, 2}, k::Int, m::AbstractMatrix, locs::Vector, control_locs, control_vals) where T
if length(control_locs) == 0
return add_matrix!(eb, k, m, locs)
end
Expand Down Expand Up @@ -169,24 +171,24 @@ Read-write complexity: 2^6.0
"""
function yao2einsum(circuit::AbstractBlock{D}; initial_state::Dict=Dict{Int,Int}(), final_state::Dict=Dict{Int,Int}(), optimizer=TreeSA()) where {D}
T = promote_type(ComplexF64, dict_regtype(initial_state), dict_regtype(final_state), YaoBlocks.parameters_eltype(circuit))
vec_initial_state = Dict{keytype(initial_state),ArrayReg{D,T}}([k=>render_single_qubit_state(T, v) for (k, v) in initial_state])
vec_final_state = Dict{keytype(final_state),ArrayReg{D,T}}([k=>render_single_qubit_state(T, v) for (k, v) in final_state])
vec_initial_state = Dict{keytype(initial_state),ArrayReg{D,T}}([k=>render_single_qudit_state(T, D, v) for (k, v) in initial_state])
vec_final_state = Dict{keytype(final_state),ArrayReg{D,T}}([k=>render_single_qudit_state(T, D, v) for (k, v) in final_state])
yao2einsum(circuit, vec_initial_state, vec_final_state, optimizer)
end
dict_regtype(d::Dict) = promote_type(_regtype.(values(d))...)
_regtype(::ArrayReg{D,VT}) where {D,VT} = VT
_regtype(::Int) = ComplexF64
render_single_qubit_state(::Type{T}, x::Int) where T = x == 0 ? zero_state(T, 1) : product_state(T, bit"1")
render_single_qubit_state(::Type{T}, x::ArrayReg) where T = ArrayReg(collect(T, statevec(x)))
render_single_qudit_state(::Type{T}, D, x::Int) where T = product_state(T, DitStr{D}([x]))
render_single_qudit_state(::Type{T}, D, x::ArrayReg) where T = ArrayReg{D}(collect(T, statevec(x)))

function yao2einsum(circuit::AbstractBlock{D}, initial_state::Dict{Int,<:ArrayReg{D,T}}, final_state::Dict{Int,<:ArrayReg{D,T}}, optimizer) where {D,T}
v_initial_state = Dict{Vector{Int}, ArrayReg{D,T}}([[k]=>v for (k, v) in initial_state])
v_final_state = Dict{Vector{Int}, ArrayReg{D, T}}([[k]=>v for (k, v) in final_state])
yao2einsum(circuit, v_initial_state, v_final_state, optimizer)
end
function yao2einsum(circuit::AbstractBlock{D}, initial_state::Dict{Vector{Int},<:ArrayReg{D,T}}, final_state::Dict{Vector{Int},<:ArrayReg{D,T}}, optimizer) where {D,T}
n = nqubits(circuit)
eb = EinBuilder(T, n)
n = nqudits(circuit)
eb = EinBuilder{T, D}(n)
openindices = add_states!(eb, initial_state)
add_gate!(eb, circuit)
openindices2 = add_states!(eb, final_state; conjugate=true)
Expand All @@ -199,7 +201,7 @@ function check_state_spec(state::Dict{Vector{Int},<:ArrayReg{D,T}}, n::Int) wher
@assert all(1 .<= iks .<= n) "state qubit indices must be in the range 1 to $n"
return iks
end
function add_states!(eb::EinBuilder{T}, states::Dict; conjugate=false) where {T}
function add_states!(eb::EinBuilder{T, D}, states::Dict; conjugate=false) where {T, D}
n = nqubits(eb)
unique_indices = check_state_spec(states, n)
openindices = eb.slots[setdiff(1:n, unique_indices)]
Expand Down
23 changes: 22 additions & 1 deletion lib/YaoToEinsum/test/circuitmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ end
inner = (2,3)
focus!(reg, inner)
for final_state in [Dict([i=>rand_state(1) for i in inner]), Dict([i=>1 for i in inner])]
freg = join(YaoToEinsum.render_single_qubit_state(ComplexF64, final_state[3]), YaoToEinsum.render_single_qubit_state(ComplexF64, final_state[2]))
freg = join(YaoToEinsum.render_single_qudit_state(ComplexF64, 2, final_state[3]), YaoToEinsum.render_single_qudit_state(ComplexF64, 2, final_state[2]))
net = yao2einsum(c; initial_state=initial_state, final_state=final_state, optimizer=TreeSA(nslices=3))
println(net)
@test vec(contract(net)) ≈ vec(statevec(freg)' * state(reg))
Expand Down Expand Up @@ -347,4 +347,25 @@ end
code, xs = yao2einsum(c; initial_state=Dict([1, 2]=>reg1, [3, 4]=>reg2), final_state=Dict([1]=>reg3, [2,3,4]=>reg4))
@test code(xs...; size_info=uniformsize(code, 2))[] ≈ join(reg4, reg3)' * join(reg2, reg1)
end

@testset "multi-level" begin
N2 = OnLevels{3}(ConstGate.P1, (2, 3))
X01 = OnLevels{3}(ConstGate.P1, (1, 2))
X12 = OnLevels{3}(ConstGate.P1, (2, 3))
function qaoa_circuit(nbits::Int, depth::Int)
n2 = chain([kron(nbits, i=>N2, i+1=>N2) for i=1:nbits-1])
x01 = chain([put(nbits, i=>X01) for i=1:nbits])
x12 = chain([put(nbits, i=>X12) for i=1:nbits])
return chain(repeat([n2, x01, x12], depth))
end

c = qaoa_circuit(5, 2)
op = repeat(5, X01)
extc = chain(c, op, c')

res = yao2einsum(extc, initial_state=Dict(zip(1:5, zeros(Int, 5))), final_state=Dict(zip(1:5, zeros(Int, 5))))
@test res isa TensorNetwork
expected = expect(op, zero_state(ComplexF64, 5; nlevel=3) |> c)
@test res.code(res.tensors...; size_info=uniformsize(res.code, 3))[] ≈ expected
end
end
23 changes: 15 additions & 8 deletions src/EasyBuild/hamiltonians.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,29 @@ function transverse_ising(nbit::Int, h::Number; periodic::Bool=true)
ising_term + h*sum(map(i->put(nbit,i=>X), 1:nbit))
end

# a 3 level hamiltonian
"""
rydberg_chain(nbits::Int; Ω::Number=0.0, Δ::Real=0.0, V::Real=0.0, r::Real=0.0)

Rydberg chain hamiltonian defined as:
```math
\\sum_{i=1}^{n} \\Delta |r_i\\rangle\\langle r_i| + (\\Omega |1\\rangle\\langle r_i| + h.c.) + V n_i n_{i+1} + r (|0\\rangle\\langle 1| + h.c.)
```
where ``n`` is specified by `nbits`.
"""
function rydberg_chain(nbits::Int; Ω::Number=0.0, Δ::Real=0.0, V::Real=0.0, r::Real=0.0)
Pr = matblock(sparse([3], [3], [1.0+0im], 3, 3); nlevel=3, tag="|r⟩⟨r|")
Z1r = matblock(sparse([2, 3], [2, 3], [1.0+0im, -1.0], 3, 3); nlevel=3)
X1r = matblock(sparse([2, 3], [3, 2], [1.0+0im, 1.0], 3, 3); nlevel=3, tag="|1⟩⟨r| + |r⟩⟨1|")
X01 = matblock(sparse([1, 2], [2, 1], [1.0+0im, 1.0], 3, 3); nlevel=3, tag="|1⟩⟨0| + |0⟩⟨1|")
Y1r = matblock(sparse([3, 2], [2, 3], [1.0im, -1.0im], 3, 3); nlevel=3, tag="i|1⟩⟨0| - i|0⟩⟨1|")
Pr = OnLevels{3}(ConstGate.P1, (2, 3))
X1r = OnLevels{3}(X, (2, 3))
X01 = OnLevels{3}(X, (1, 2))
Y1r = OnLevels{3}(Y, (2, 3))
# single site term in {|1>, |r>}.
h = Add(nbits; nlevel=3)
!isapprox(Δ, 0; atol=1e-12) && push!(h, (-Δ) * sum([put(nbits, i=>Pr) for i=1:nbits]))
#!iszero(Δ) && push!(h, (-Δ/2) * sum([put(nbits, i=>Z1r) for i=1:nbits]))
!isapprox(real(Ω), 0; atol=1e-12) && push!(h, real(Ω)/2 * sum([put(nbits, i=>X1r) for i=1:nbits]))
!isapprox(imag(Ω), 0; atol=1e-12) && push!(h, imag(Ω)/2 * sum([put(nbits, i=>Y1r) for i=1:nbits]))
# interaction
!isapprox(V, 0; atol=1e-12) && nbits > 1 && push!(h, V * sum([put(nbits, (i,i+1)=>kron(Pr, Pr)) for i=1:nbits-1]))
# Raman term
!isapprox(r, 0; atol=1e-12) && push!(h, r * sum([put(nbits, i=>X01) for i=1:nbits]))
return h
end
end

5 changes: 5 additions & 0 deletions test/easybuild/hamiltonians.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ using YaoArrayRegister.SparseArrays
@test vec(h[:,bit"01001000"] |> cleanup) ≈ mat(h)[:, buffer(bit"01001000")+1]
end

@testset "rydberg_chain" begin
h = rydberg_chain(3, Ω=1.0, Δ=1.0, V=1.0, r=1.0)
@test mat(ComplexF64, h) isa SparseMatrixCSC
end

# https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.170503
# Acknology: Jonathan Wurtz and Madelyn Cain for extremely helpful discussion!
@testset "Levine Pichler pulse" begin
Expand Down
Loading