-
Notifications
You must be signed in to change notification settings - Fork 1
BP gauge for graph tensor network state #17
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| module BPGauge | ||
|
|
||
| using LinearAlgebra, Graphs, Random | ||
| using OMEinsum | ||
|
|
||
| export TensorNetworkAnsatz | ||
| export BPState, BPPath, BPStep | ||
|
|
||
| export zero_state, random_state, inner_product, normalize_state! | ||
| export bp!, bp_update! | ||
| export absorb! | ||
| export apply_gauge!, gauge! | ||
|
|
||
| export square_lattice | ||
|
|
||
| include("utils.jl") | ||
|
|
||
| # define and construct the network | ||
| include("ansatz.jl") | ||
|
|
||
| # bp on the tensor network ansatz | ||
| include("bp.jl") | ||
|
|
||
| # operations about gauging | ||
| include("gauge.jl") | ||
|
|
||
| # simple update | ||
| include("su.jl") | ||
|
|
||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # the tensor network ansatz, |ket> | ||
| struct TensorNetworkAnsatz{TA <: AbstractArray, TB <: AbstractArray} | ||
| g::SimpleGraph{Int} # g is used to store the connections between the tensors | ||
| site_tensors::Vector{TA} # the tensors are stored as vector of arrays, corresponding to vertices of the graph. The virtual bounds are in order of its neighbors, the last dimension is the open dimension | ||
| gauge_tensors::Vector{TB} # corresponding to edges of the graph, listed in the order of edges(g), and enforce e.src < e.dst, real valued diagonal matrices | ||
| gauge_tensors_map::Dict{Tuple{Int, Int}, Int} # maps between the edge and the index of the gauge tensor | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From tutorial perspective, I think maybe using |
||
|
|
||
| function TensorNetworkAnsatz(g::SimpleGraph{Int}, d_virtual::Int, d_open::Int; type::Type = Float64) | ||
| TA = Array{Complex{type}} | ||
| TB = Array{type} | ||
|
|
||
| site_tensors = Vector{TA}() | ||
| gauge_tensors = Vector{TB}() | ||
|
|
||
| for v in vertices(g) | ||
| t = zeros(Complex{type}, [d_virtual for _ in 1:length(neighbors(g, v))]..., d_open) | ||
| push!(site_tensors, t) | ||
| end | ||
|
|
||
| for e in edges(g) | ||
| t = (one(type) * I)(d_virtual) | ||
| push!(gauge_tensors, t) | ||
| end | ||
|
|
||
| map = Dict{Tuple{Int, Int}, Int}() | ||
| for (i, e) in enumerate(edges(g)) | ||
| src, dst = minmax(e.src, e.dst) | ||
| map[(src, dst)] = i | ||
| end | ||
|
|
||
| new{TA, TB}(g, site_tensors, gauge_tensors, map) | ||
| end | ||
|
|
||
| function TensorNetworkAnsatz(g::SimpleGraph{Int}, site_tensors::Vector{TA}, gauge_tensors::Vector{TB}, map::Dict{Tuple{Int, Int}, Int}) where {TA, TB} | ||
| @assert length(site_tensors) == nv(g) | ||
| @assert length(gauge_tensors) == ne(g) | ||
| new{TA, TB}(g, site_tensors, gauge_tensors, map) | ||
| end | ||
| end | ||
|
|
||
| Base.show(io::IO, ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB} = print(io, "TensorNetworkAnsatz{$(TA), $(TB)} with $(nv(ansatz.g)) sites and $(ne(ansatz.g)) edges") | ||
|
|
||
| Base.adjoint(ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB} = TensorNetworkAnsatz(ansatz.g, TA[conj(t) for t in ansatz.site_tensors], ansatz.gauge_tensors, ansatz.gauge_tensors_map) | ||
|
|
||
| function inner_product(bra::TensorNetworkAnsatz{TA, TB}, ket::TensorNetworkAnsatz{TA}; optimizer = GreedyMethod()) where {TA, TB} | ||
| @assert bra.g == ket.g | ||
|
|
||
| raw_code = inner_product_eins(bra.g) | ||
|
|
||
| xs = [bra.site_tensors..., bra.gauge_tensors..., ket.site_tensors..., ket.gauge_tensors...] | ||
| size_dict = OMEinsum.get_size_dict!(raw_code.ixs, xs, Dict{Int, Int}()) | ||
|
|
||
| opt_code = optimize_code(raw_code, size_dict, optimizer) | ||
| # @info "contraction complexity: $(contraction_complexity(opt_code, size_dict))" | ||
|
|
||
| res = opt_code(xs...) | ||
| return res[] | ||
| end | ||
|
|
||
| # initialize the state for rydberg system, all sites are in the |0> state | ||
| function zero_state(g::SimpleGraph{Int}; d_virtual::Int = 1) | ||
| phi = TensorNetworkAnsatz(g, d_virtual, 2) | ||
| # set all to the |0> state | ||
| for tensor in phi.site_tensors | ||
| tensor[1] = ComplexF64(1.0) | ||
| end | ||
| return phi | ||
| end | ||
|
|
||
| # generate non-normalized random state | ||
| function random_state(g::SimpleGraph{Int}; d_virtual::Int = 1) | ||
| phi = TensorNetworkAnsatz(g, d_virtual, 2) | ||
| for tensor in phi.site_tensors | ||
| tensor .= rand(ComplexF64, size(tensor)) | ||
| end | ||
| return phi | ||
| end | ||
|
|
||
| function normalize_state!(phi::TensorNetworkAnsatz{TA, TB}) where {TA, TB} | ||
| n = inner_product(phi, adjoint(phi)) | ||
| phi.site_tensors .= phi.site_tensors ./ (sqrt(real(n))^(1 / nv(phi.g))) | ||
| return phi | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # bp on the TensorNetworkAnsatz | ||
|
|
||
| # each message tensor have two legs, as follows | ||
| # --Ta -- |--1--Tb -- | ||
| # Mab | ||
| # --Ta*-- |--2--Tb*-- | ||
| struct BPState{TA} | ||
| messages::Dict{Tuple{Int, Int}, TA} | ||
| function BPState(ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB} | ||
| messages = Dict{Tuple{Int, Int}, TA}() | ||
| TE = eltype(TA) | ||
|
|
||
| # initialize the messages from neighbors to sites | ||
| for dst in vertices(ansatz.g) | ||
| for (i, src) in enumerate(neighbors(ansatz.g, dst)) | ||
| d_virtual = size(ansatz.site_tensors[dst])[i] | ||
| messages[(src, dst)] = ones(TE, d_virtual, d_virtual) | ||
| normalize_message!(messages[(src, dst)]) | ||
| end | ||
| end | ||
|
|
||
| new{TA}(messages) | ||
| end | ||
| end | ||
|
|
||
| # a single bp update step: \sum_{T_neighbors} T * T* * M_inputs... -> M_output | ||
| struct BPStep | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add more descriptions to data structures to improved code readability. |
||
| eincode::AbstractEinsum # Eincode([T..., T*..., M_inputs...], [M_output]) | ||
| Tid::Int # the id of the tensor to interact with | ||
| inputs::Vector{Tuple{Int, Int}} # input messages | ||
| output::Tuple{Int, Int} # output message | ||
| end | ||
|
|
||
| struct BPPath | ||
| bp_steps::Vector{BPStep} # steps of bp update, each step is a single bp update | ||
| function BPPath(ansatz::TensorNetworkAnsatz{TA, TB}; random_order::Bool = true, seed::Int = 1234) where {TA, TB} | ||
| g = ansatz.g | ||
| Random.seed!(seed) | ||
| bp_steps = BPStep[] | ||
| # the different step are taken in random order | ||
| outputs = Tuple{Int, Int}[] | ||
| for e in edges(g) | ||
| push!(outputs, (src(e), dst(e))) | ||
| push!(outputs, (dst(e), src(e))) | ||
| end | ||
|
|
||
| # the steps are taken in random order | ||
| random_order && shuffle!(outputs) | ||
|
|
||
| for (s, d) in outputs | ||
| inputs = Tuple{Int, Int}[] | ||
| Tid = s | ||
| for v in neighbors(g, s) | ||
| if v != d | ||
| push!(inputs, (v, s)) | ||
| end | ||
| end | ||
|
|
||
| eincode = bp_eins(neighbors(g, s), inputs, (s, d)) | ||
| push!(bp_steps, BPStep(eincode, Tid, inputs, (s, d))) | ||
| end | ||
|
|
||
| new(bp_steps) | ||
| end | ||
| end | ||
|
|
||
| function bp!(state::BPState, path::BPPath, ansatz::TensorNetworkAnsatz; max_iter::Int = 1000, atol::Float64 = 1e-6, damping::Float64 = 0.2, verbose::Bool = false) | ||
| Ts = ansatz.site_tensors | ||
| Tcs = conj.(ansatz.site_tensors) | ||
| for iter in 1:max_iter | ||
| error = bp_update!(state, path, Ts, Tcs, damping) | ||
| verbose && @info "BP iteration $iter, error: $error" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not recommend adding verbose parameters. Is it possible to use the Julia logging system here? https://docs.julialang.org/en/v1/stdlib/Logging/ |
||
| error < atol && break | ||
| end | ||
| nothing | ||
| end | ||
|
|
||
| function bp_update!(state::BPState, path::BPPath, Ts::Vector{TA}, Tcs::Vector{TB}, damping::Float64) where {TA, TB} | ||
|
|
||
| error = 0.0 | ||
| for step in path.bp_steps | ||
| eincode = step.eincode | ||
| Tid = step.Tid | ||
| inputs = step.inputs | ||
| output = step.output | ||
|
|
||
| T = Ts[Tid] | ||
| Tc = Tcs[Tid] | ||
|
|
||
| M_inputs = [state.messages[input] for input in inputs] | ||
|
|
||
| # update the message tensor | ||
| new_message = normalize_message!(eincode(T, Tc, M_inputs...)) | ||
| error = max(error, maximum(abs.(state.messages[output] - new_message))) | ||
| state.messages[output] .*= damping | ||
| state.messages[output] .+= (1 - damping) * new_message | ||
| end | ||
| return error | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # absorb the gauge tensors (diagonal matrices) into the site tensors, for bp to work | ||
| # after the gauge is absorbed, set them to identity | ||
| function absorb!(ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB} | ||
| for e in edges(ansatz.g) | ||
| s, d = minmax(e.src, e.dst) | ||
| Gamma = ansatz.gauge_tensors[ansatz.gauge_tensors_map[(s, d)]] | ||
| sqrt_Gamma = sqrt.(Gamma) | ||
|
|
||
| eincode_s, eincode_d = absorb_eins(s, d, neighbors(ansatz.g, s), neighbors(ansatz.g, d)) | ||
|
|
||
| s_xs = (ansatz.site_tensors[s], sqrt_Gamma) | ||
| sds = OMEinsum.get_size_dict(eincode_s.ixs, s_xs) | ||
| einsum!(eincode_s.ixs, eincode_s.iy, s_xs, ansatz.site_tensors[s], 1, 0, sds) | ||
|
|
||
| d_xs = (ansatz.site_tensors[d], sqrt_Gamma) | ||
| sdd = OMEinsum.get_size_dict(eincode_d.ixs, d_xs) | ||
| einsum!(eincode_d.ixs, eincode_d.iy, d_xs, ansatz.site_tensors[d], 1, 0, sdd) | ||
|
|
||
| # reset the gauge tensor to identity | ||
| Gamma .= I(size(Gamma, 1)) | ||
| end | ||
| nothing | ||
| end | ||
|
|
||
| # assume that the existing gauge tensors are already absorbed into the site tensors | ||
| # the bp converge, using the bp messages to generate the new gauge tensors, which are real valued diagonal matrices | ||
| # according to Eqs.12~17 of https://scipost.org/SciPostPhys.15.6.222 | ||
| # site s and site d | ||
| # Ts -i- sqrt_Msd_inv (j, i) -j- sqrt_Msd (k, j) -k- sqrt_Mds (k, l) -l- sqrt_Mds_inv (l, m) -m- Td | ||
| # Ts -i- sqrt_Msd_inv (j, i) -j- U(j, n) -n- S(n, o) -o- Vt(o, l) -l- sqrt_Mds_inv (l, m) -m- Td | ||
| # Ts -i- As (i, n) -n- Gamma -o- Ad (m, o) -m- Td | ||
| # Ts -i- Gamma -m- Td | ||
| function gauge!(ansatz::TensorNetworkAnsatz{TA, TB}, state::BPState{TA}) where {TA, TB} | ||
| for e in edges(ansatz.g) | ||
| s, d = minmax(e.src, e.dst) | ||
| M_sd = state.messages[(s, d)] | ||
| M_ds = state.messages[(d, s)] | ||
|
|
||
| apply_gauge!(ansatz, s, d, M_sd, M_ds) | ||
| end | ||
| nothing | ||
| end | ||
|
|
||
| # given the message matrix M_sd and M_ds, generate the new gauge tensor Gamma, update T_s and T_d | ||
| function apply_gauge!(ansatz::TensorNetworkAnsatz{TA, TB}, s::Int, d::Int, M_sd::Matrix{T}, M_ds::Matrix{T}) where {TA, TB, T} | ||
| # square root the message matrix | ||
| sqrt_Msd, sqrt_Msd_inv = square_root(M_sd) | ||
| sqrt_Mds, sqrt_Mds_inv = square_root(M_ds) | ||
|
|
||
| # the eigen decomposition is only correct when the message matrix is positive definite | ||
|
GiggleLiu marked this conversation as resolved.
|
||
| # @show ein"ji, kj -> ik"(sqrt_Msd_inv, sqrt_Msd) ≈ I(size(M_sd, 1)) | ||
| # @show ein"kl, lm -> km"(sqrt_Mds, sqrt_Mds_inv) ≈ I(size(M_sd, 1)) | ||
| # @show ein"ji, kj, kl, lm -> im"(sqrt_Msd_inv, sqrt_Msd, sqrt_Mds, sqrt_Mds_inv) ≈ I(size(M_sd, 1)) | ||
|
|
||
| # generate the new gauge tensor | ||
| M_mid = ein"kj, kl -> jl"(sqrt_Msd, sqrt_Mds) | ||
|
|
||
| # @show ein"ji, jl, lm -> im"(sqrt_Msd_inv, M_mid, sqrt_Mds_inv) ≈ I(size(M_mid, 1)) | ||
|
|
||
| res = svd(M_mid) | ||
| U = res.U # jn | ||
| S = diagm(res.S) # no | ||
| Vt = res.Vt # ol | ||
|
|
||
| # @show ein"jn, no, ol -> jl"(U, S, Vt) ≈ M_mid | ||
| # @show maximum(abs.(ein"ji, jn, no, ol, lm -> im"(sqrt_Msd_inv, U, S, Vt, sqrt_Mds_inv) - I(size(M_mid, 1)))) | ||
|
|
||
| As = ein"ji, jn -> in"(sqrt_Msd_inv, U) | ||
| Ad = ein"lm, ol -> mo"(sqrt_Mds_inv, Vt) | ||
|
|
||
| # @show maximum(abs.(ein"in, no, mo -> im"(As, S, Ad) - I(size(As, 1)))) | ||
|
|
||
| #update the gauge tensor | ||
| ansatz.gauge_tensors[ansatz.gauge_tensors_map[(s, d)]] .= S | ||
|
|
||
| # absorb As and Ad into the site tensors | ||
| eincode_s, eincode_d = absorb_eins(s, d, neighbors(ansatz.g, s), neighbors(ansatz.g, d)) | ||
| s_xs = (ansatz.site_tensors[s], As) | ||
| sds = OMEinsum.get_size_dict(eincode_s.ixs, s_xs) | ||
| einsum!(eincode_s.ixs, eincode_s.iy, s_xs, ansatz.site_tensors[s], 1, 0, sds) | ||
|
|
||
| d_xs = (ansatz.site_tensors[d], Ad) | ||
| sdd = OMEinsum.get_size_dict(eincode_d.ixs, d_xs) | ||
| einsum!(eincode_d.ixs, eincode_d.iy, d_xs, ansatz.site_tensors[d], 1, 0, sdd) | ||
|
|
||
| nothing | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # TODO | ||
| # 1. single site update | ||
| # 2. two site update | ||
| # 3. truncation |
Uh oh!
There was an error while loading. Please reload this page.