Skip to content

Commit 0e637df

Browse files
authored
Merge pull request #17 from CodingThrust/dev
BP gauge for graph tensor network state
2 parents 1682fa1 + 9ec6b7d commit 0e637df

13 files changed

Lines changed: 725 additions & 8 deletions

File tree

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
# BPGauge
22

3-
<!-- [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://ArrogantGao.github.io/BPGauge.jl/stable/) -->
4-
<!-- [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://ArrogantGao.github.io/BPGauge.jl/dev/) -->
5-
<!-- [![Build Status](https://github.com/ArrogantGao/BPGauge.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/ArrogantGao/BPGauge.jl/actions/workflows/CI.yml?query=branch%3Amain) -->
6-
<!-- [![Coverage](https://codecov.io/gh/ArrogantGao/BPGauge.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/ArrogantGao/BPGauge.jl) -->
3+
[![Build Status](https://github.com/CodingThrust/BPGauge.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/CodingThrust/BPGauge.jl/actions/workflows/CI.yml?query=branch%3Amain)
4+
<!-- [![Coverage](https://codecov.io/gh/CodingThrust/BPGauge.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/CodingThrust/BPGauge.jl) -->
75

86
## Introduction
97

@@ -13,7 +11,7 @@ A simple example is to consider a cycle graph:
1311
```julia
1412
using BPGauge, Graphs
1513

16-
g = chain(100)
14+
g = path_graph(100)
1715
add_edge!(g, 1, 100)
1816

1917
# generate a random state
@@ -23,7 +21,7 @@ normalize_state!(tn)
2321
# generate a BP state, and do BP until convergence
2422
bp_state = BPState(tn)
2523
bp_path = BPPath(tn)
26-
bp!(bp_state, bp_path, tn, err_bound = 1e-8)
24+
bp!(bp_state, bp_path, tn, atol = 1e-8)
2725

2826
# gauge transform the state
2927
gauge!(tn, bp_state)

exm/Note.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ More complex geometry and property will reduce more the dimension of system by i
1212

1313
$D=2^N \xrightarrow{\text{Constraint}} \alpha^N \xrightarrow{\text{Particle Number}}\alpha^{N-1}/k\xrightarrow{\text{translation}}\alpha^{N-1}/kN\xrightarrow{\text{spin flip/inversion}}\alpha^{N-2}/kN$.
1414

15-
where $\alpha \sim 1.618, 1.502$ for Fibonacci chain, hard core square lattice, $k \sim 5$. So for fully ED, $D \sim 10^6$. When doing dynamics, Krylov subspace $K_m​=\text{span}\{v,Hv,H^2v,⋯,H^{m−1}v\}$ is usually incorporated, which can scale up to $N=32$ even without anyon symmetry ($D \sim 10^{10}, m \sim 10^4$, as long as the locality of interaction ensuring sparse matrix). In such basis, the Its complexity decreases to $O(m^2D)$
15+
where $\alpha \sim 1.618, 1.502$ for Fibonacci path_graph, hard core square lattice, $k \sim 5$. So for fully ED, $D \sim 10^6$. When doing dynamics, Krylov subspace $K_m​=\text{span}\{v,Hv,H^2v,⋯,H^{m−1}v\}$ is usually incorporated, which can scale up to $N=32$ even without anyon symmetry ($D \sim 10^{10}, m \sim 10^4$, as long as the locality of interaction ensuring sparse matrix). In such basis, the Its complexity decreases to $O(m^2D)$
1616

1717

1818
## *Tensor Networks, TN*
@@ -95,7 +95,7 @@ Supplemental Material](https://arxiv.org/pdf/1911.04882), which maybe helpful?
9595
<img width="872" alt="Image" src="https://github.com/user-attachments/assets/df2cf6ed-da28-4320-9f28-335b383a5cbb" />
9696

9797
[^Leshouches]: Les houch notes for Exact Diagonalizaton. https://indico.ictp.it/event/a14246/session/31/contribution/51/material/0/0.pdf
98-
[^QMBSPRB]: Quantum scarred eigenstates in a Rydberg atom chain: Entanglement, breakdown of thermalization, and stability to perturbations. https://journals.aps.org/prb/pdf/10.1103/PhysRevB.98.155134
98+
[^QMBSPRB]: Quantum scarred eigenstates in a Rydberg atom path_graph: Entanglement, breakdown of thermalization, and stability to perturbations. https://journals.aps.org/prb/pdf/10.1103/PhysRevB.98.155134
9999
[^LauchliKagome]: S = 1/2 kagome Heisenberg antiferromagnet revisited https://journals.aps.org/prb/pdf/10.1103/PhysRevB.100.155142
100100
[^Dowling]: Monte Carlo techniques for real-time
101101
quantum dynamics https://arxiv.org/pdf/quant-ph/0507003

src/BPGauge.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module BPGauge
2+
3+
using LinearAlgebra, Graphs, Random
4+
using OMEinsum
5+
6+
export TensorNetworkAnsatz
7+
export BPState, BPPath, BPStep
8+
9+
export zero_state, random_state, inner_product, normalize_state!
10+
export bp!, bp_update!
11+
export absorb!
12+
export apply_gauge!, gauge!
13+
14+
export square_lattice
15+
16+
include("utils.jl")
17+
18+
# define and construct the network
19+
include("ansatz.jl")
20+
21+
# bp on the tensor network ansatz
22+
include("bp.jl")
23+
24+
# operations about gauging
25+
include("gauge.jl")
26+
27+
# simple update
28+
include("su.jl")
29+
30+
end

src/ansatz.jl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# the tensor network ansatz, |ket>
2+
struct TensorNetworkAnsatz{TA <: AbstractArray, TB <: AbstractArray}
3+
g::SimpleGraph{Int} # g is used to store the connections between the tensors
4+
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
5+
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
6+
gauge_tensors_map::Dict{Tuple{Int, Int}, Int} # maps between the edge and the index of the gauge tensor
7+
8+
function TensorNetworkAnsatz(g::SimpleGraph{Int}, d_virtual::Int, d_open::Int; type::Type = Float64)
9+
TA = Array{Complex{type}}
10+
TB = Array{type}
11+
12+
site_tensors = Vector{TA}()
13+
gauge_tensors = Vector{TB}()
14+
15+
for v in vertices(g)
16+
t = zeros(Complex{type}, [d_virtual for _ in 1:length(neighbors(g, v))]..., d_open)
17+
push!(site_tensors, t)
18+
end
19+
20+
for e in edges(g)
21+
t = (one(type) * I)(d_virtual)
22+
push!(gauge_tensors, t)
23+
end
24+
25+
map = Dict{Tuple{Int, Int}, Int}()
26+
for (i, e) in enumerate(edges(g))
27+
src, dst = minmax(e.src, e.dst)
28+
map[(src, dst)] = i
29+
end
30+
31+
new{TA, TB}(g, site_tensors, gauge_tensors, map)
32+
end
33+
34+
function TensorNetworkAnsatz(g::SimpleGraph{Int}, site_tensors::Vector{TA}, gauge_tensors::Vector{TB}, map::Dict{Tuple{Int, Int}, Int}) where {TA, TB}
35+
@assert length(site_tensors) == nv(g)
36+
@assert length(gauge_tensors) == ne(g)
37+
new{TA, TB}(g, site_tensors, gauge_tensors, map)
38+
end
39+
end
40+
41+
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")
42+
43+
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)
44+
45+
function inner_product(bra::TensorNetworkAnsatz{TA, TB}, ket::TensorNetworkAnsatz{TA}; optimizer = GreedyMethod()) where {TA, TB}
46+
@assert bra.g == ket.g
47+
48+
raw_code = inner_product_eins(bra.g)
49+
50+
xs = [bra.site_tensors..., bra.gauge_tensors..., ket.site_tensors..., ket.gauge_tensors...]
51+
size_dict = OMEinsum.get_size_dict!(raw_code.ixs, xs, Dict{Int, Int}())
52+
53+
opt_code = optimize_code(raw_code, size_dict, optimizer)
54+
# @info "contraction complexity: $(contraction_complexity(opt_code, size_dict))"
55+
56+
res = opt_code(xs...)
57+
return res[]
58+
end
59+
60+
# initialize the state for rydberg system, all sites are in the |0> state
61+
function zero_state(g::SimpleGraph{Int}; d_virtual::Int = 1)
62+
phi = TensorNetworkAnsatz(g, d_virtual, 2)
63+
# set all to the |0> state
64+
for tensor in phi.site_tensors
65+
tensor[1] = ComplexF64(1.0)
66+
end
67+
return phi
68+
end
69+
70+
# generate non-normalized random state
71+
function random_state(g::SimpleGraph{Int}; d_virtual::Int = 1)
72+
phi = TensorNetworkAnsatz(g, d_virtual, 2)
73+
for tensor in phi.site_tensors
74+
tensor .= rand(ComplexF64, size(tensor))
75+
end
76+
return phi
77+
end
78+
79+
function normalize_state!(phi::TensorNetworkAnsatz{TA, TB}) where {TA, TB}
80+
n = inner_product(phi, adjoint(phi))
81+
phi.site_tensors .= phi.site_tensors ./ (sqrt(real(n))^(1 / nv(phi.g)))
82+
return phi
83+
end

src/bp.jl

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# bp on the TensorNetworkAnsatz
2+
3+
# each message tensor have two legs, as follows
4+
# --Ta -- |--1--Tb --
5+
# Mab
6+
# --Ta*-- |--2--Tb*--
7+
struct BPState{TA}
8+
messages::Dict{Tuple{Int, Int}, TA}
9+
function BPState(ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB}
10+
messages = Dict{Tuple{Int, Int}, TA}()
11+
TE = eltype(TA)
12+
13+
# initialize the messages from neighbors to sites
14+
for dst in vertices(ansatz.g)
15+
for (i, src) in enumerate(neighbors(ansatz.g, dst))
16+
d_virtual = size(ansatz.site_tensors[dst])[i]
17+
messages[(src, dst)] = ones(TE, d_virtual, d_virtual)
18+
normalize_message!(messages[(src, dst)])
19+
end
20+
end
21+
22+
new{TA}(messages)
23+
end
24+
end
25+
26+
# a single bp update step: \sum_{T_neighbors} T * T* * M_inputs... -> M_output
27+
struct BPStep
28+
eincode::AbstractEinsum # Eincode([T..., T*..., M_inputs...], [M_output])
29+
Tid::Int # the id of the tensor to interact with
30+
inputs::Vector{Tuple{Int, Int}} # input messages
31+
output::Tuple{Int, Int} # output message
32+
end
33+
34+
struct BPPath
35+
bp_steps::Vector{BPStep} # steps of bp update, each step is a single bp update
36+
function BPPath(ansatz::TensorNetworkAnsatz{TA, TB}; random_order::Bool = true, seed::Int = 1234) where {TA, TB}
37+
g = ansatz.g
38+
Random.seed!(seed)
39+
bp_steps = BPStep[]
40+
# the different step are taken in random order
41+
outputs = Tuple{Int, Int}[]
42+
for e in edges(g)
43+
push!(outputs, (src(e), dst(e)))
44+
push!(outputs, (dst(e), src(e)))
45+
end
46+
47+
# the steps are taken in random order
48+
random_order && shuffle!(outputs)
49+
50+
for (s, d) in outputs
51+
inputs = Tuple{Int, Int}[]
52+
Tid = s
53+
for v in neighbors(g, s)
54+
if v != d
55+
push!(inputs, (v, s))
56+
end
57+
end
58+
59+
eincode = bp_eins(neighbors(g, s), inputs, (s, d))
60+
push!(bp_steps, BPStep(eincode, Tid, inputs, (s, d)))
61+
end
62+
63+
new(bp_steps)
64+
end
65+
end
66+
67+
function bp!(state::BPState, path::BPPath, ansatz::TensorNetworkAnsatz; max_iter::Int = 1000, atol::Float64 = 1e-6, damping::Float64 = 0.2, verbose::Bool = false)
68+
Ts = ansatz.site_tensors
69+
Tcs = conj.(ansatz.site_tensors)
70+
for iter in 1:max_iter
71+
error = bp_update!(state, path, Ts, Tcs, damping)
72+
verbose && @info "BP iteration $iter, error: $error"
73+
error < atol && break
74+
end
75+
nothing
76+
end
77+
78+
function bp_update!(state::BPState, path::BPPath, Ts::Vector{TA}, Tcs::Vector{TB}, damping::Float64) where {TA, TB}
79+
80+
error = 0.0
81+
for step in path.bp_steps
82+
eincode = step.eincode
83+
Tid = step.Tid
84+
inputs = step.inputs
85+
output = step.output
86+
87+
T = Ts[Tid]
88+
Tc = Tcs[Tid]
89+
90+
M_inputs = [state.messages[input] for input in inputs]
91+
92+
# update the message tensor
93+
new_message = normalize_message!(eincode(T, Tc, M_inputs...))
94+
error = max(error, maximum(abs.(state.messages[output] - new_message)))
95+
state.messages[output] .*= damping
96+
state.messages[output] .+= (1 - damping) * new_message
97+
end
98+
return error
99+
end

src/gauge.jl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# absorb the gauge tensors (diagonal matrices) into the site tensors, for bp to work
2+
# after the gauge is absorbed, set them to identity
3+
function absorb!(ansatz::TensorNetworkAnsatz{TA, TB}) where {TA, TB}
4+
for e in edges(ansatz.g)
5+
s, d = minmax(e.src, e.dst)
6+
Gamma = ansatz.gauge_tensors[ansatz.gauge_tensors_map[(s, d)]]
7+
sqrt_Gamma = sqrt.(Gamma)
8+
9+
eincode_s, eincode_d = absorb_eins(s, d, neighbors(ansatz.g, s), neighbors(ansatz.g, d))
10+
11+
s_xs = (ansatz.site_tensors[s], sqrt_Gamma)
12+
sds = OMEinsum.get_size_dict(eincode_s.ixs, s_xs)
13+
einsum!(eincode_s.ixs, eincode_s.iy, s_xs, ansatz.site_tensors[s], 1, 0, sds)
14+
15+
d_xs = (ansatz.site_tensors[d], sqrt_Gamma)
16+
sdd = OMEinsum.get_size_dict(eincode_d.ixs, d_xs)
17+
einsum!(eincode_d.ixs, eincode_d.iy, d_xs, ansatz.site_tensors[d], 1, 0, sdd)
18+
19+
# reset the gauge tensor to identity
20+
Gamma .= I(size(Gamma, 1))
21+
end
22+
nothing
23+
end
24+
25+
# assume that the existing gauge tensors are already absorbed into the site tensors
26+
# the bp converge, using the bp messages to generate the new gauge tensors, which are real valued diagonal matrices
27+
# according to Eqs.12~17 of https://scipost.org/SciPostPhys.15.6.222
28+
# site s and site d
29+
# 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
30+
# 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
31+
# Ts -i- As (i, n) -n- Gamma -o- Ad (m, o) -m- Td
32+
# Ts -i- Gamma -m- Td
33+
function gauge!(ansatz::TensorNetworkAnsatz{TA, TB}, state::BPState{TA}) where {TA, TB}
34+
for e in edges(ansatz.g)
35+
s, d = minmax(e.src, e.dst)
36+
M_sd = state.messages[(s, d)]
37+
M_ds = state.messages[(d, s)]
38+
39+
apply_gauge!(ansatz, s, d, M_sd, M_ds)
40+
end
41+
nothing
42+
end
43+
44+
# given the message matrix M_sd and M_ds, generate the new gauge tensor Gamma, update T_s and T_d
45+
function apply_gauge!(ansatz::TensorNetworkAnsatz{TA, TB}, s::Int, d::Int, M_sd::Matrix{T}, M_ds::Matrix{T}) where {TA, TB, T}
46+
# square root the message matrix
47+
sqrt_Msd, sqrt_Msd_inv = square_root(M_sd)
48+
sqrt_Mds, sqrt_Mds_inv = square_root(M_ds)
49+
50+
# the eigen decomposition is only correct when the message matrix is positive definite
51+
# @show ein"ji, kj -> ik"(sqrt_Msd_inv, sqrt_Msd) ≈ I(size(M_sd, 1))
52+
# @show ein"kl, lm -> km"(sqrt_Mds, sqrt_Mds_inv) ≈ I(size(M_sd, 1))
53+
# @show ein"ji, kj, kl, lm -> im"(sqrt_Msd_inv, sqrt_Msd, sqrt_Mds, sqrt_Mds_inv) ≈ I(size(M_sd, 1))
54+
55+
# generate the new gauge tensor
56+
M_mid = ein"kj, kl -> jl"(sqrt_Msd, sqrt_Mds)
57+
58+
# @show ein"ji, jl, lm -> im"(sqrt_Msd_inv, M_mid, sqrt_Mds_inv) ≈ I(size(M_mid, 1))
59+
60+
res = svd(M_mid)
61+
U = res.U # jn
62+
S = diagm(res.S) # no
63+
Vt = res.Vt # ol
64+
65+
# @show ein"jn, no, ol -> jl"(U, S, Vt) ≈ M_mid
66+
# @show maximum(abs.(ein"ji, jn, no, ol, lm -> im"(sqrt_Msd_inv, U, S, Vt, sqrt_Mds_inv) - I(size(M_mid, 1))))
67+
68+
As = ein"ji, jn -> in"(sqrt_Msd_inv, U)
69+
Ad = ein"lm, ol -> mo"(sqrt_Mds_inv, Vt)
70+
71+
# @show maximum(abs.(ein"in, no, mo -> im"(As, S, Ad) - I(size(As, 1))))
72+
73+
#update the gauge tensor
74+
ansatz.gauge_tensors[ansatz.gauge_tensors_map[(s, d)]] .= S
75+
76+
# absorb As and Ad into the site tensors
77+
eincode_s, eincode_d = absorb_eins(s, d, neighbors(ansatz.g, s), neighbors(ansatz.g, d))
78+
s_xs = (ansatz.site_tensors[s], As)
79+
sds = OMEinsum.get_size_dict(eincode_s.ixs, s_xs)
80+
einsum!(eincode_s.ixs, eincode_s.iy, s_xs, ansatz.site_tensors[s], 1, 0, sds)
81+
82+
d_xs = (ansatz.site_tensors[d], Ad)
83+
sdd = OMEinsum.get_size_dict(eincode_d.ixs, d_xs)
84+
einsum!(eincode_d.ixs, eincode_d.iy, d_xs, ansatz.site_tensors[d], 1, 0, sdd)
85+
86+
nothing
87+
end

src/su.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# TODO
2+
# 1. single site update
3+
# 2. two site update
4+
# 3. truncation

0 commit comments

Comments
 (0)