-
Notifications
You must be signed in to change notification settings - Fork 26
Belief propagation gauge fixing #223
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
cf42e16
9765a5c
6a17656
ccae695
247aa59
c00f847
8b91621
96c3535
48942df
5f668b3
3243ec8
baaa600
9b41f64
32a8ecc
9c47930
60939e7
d03e1f0
74b987e
88824b6
b216bc5
feb9505
8cb1bb4
b15a5a7
9a94435
14675d4
1f5997b
8f3dbff
e5d17a8
d45065b
63e7884
5f168f0
f56343c
d989a4c
6c6a47c
21ce542
8480184
c8a950b
d956d93
0742297
1515491
a27dca1
e8cc860
e7b2b26
0979971
a42e715
826ef10
590bdb4
e4554f2
3635a4c
bba8163
ea866df
37adcfb
bd34b9b
c3b49f8
11daeae
32a2f8f
6c239e0
ae0496f
9fcb13b
0b6fc6e
412726b
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 |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ jobs: | |
| - 'lts' # minimal supported version | ||
| - '1' | ||
| group: | ||
| - bp | ||
| - types | ||
| - ctmrg | ||
| - boundarymps | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| """ | ||
| struct BeliefPropagation | ||
|
|
||
| Algorithm for computing the belief propagation fixed point messages. | ||
|
|
||
| ## Fields | ||
|
|
||
| $(TYPEDFIELDS) | ||
| """ | ||
| @kwdef struct BeliefPropagation | ||
| "Stopping criterion for the BP iterations in relative trace norm difference" | ||
| tol::Float64 = 1.0e-6 | ||
|
|
||
| "Minimal number of BP iterations" | ||
| miniter::Int = 2 | ||
|
|
||
| "Maximal number of BP iterations" | ||
| maxiter::Int = 50 | ||
|
|
||
| "Toggle for projecting messages onto the hermitian subspace immediately after update through BP equation" | ||
| project_hermitian::Bool = true | ||
|
|
||
| "Output verbosity level" | ||
| verbosity::Int = 2 | ||
| end | ||
|
|
||
| """ | ||
| leading_boundary(env₀::BPEnv, network, alg::BeliefPropagation) | ||
|
|
||
| Contract `network` in the BP approximation and return the corresponding messages. | ||
| """ | ||
| function leading_boundary(env₀::BPEnv, network::InfiniteSquareNetwork, alg::BeliefPropagation) | ||
| return LoggingExtras.withlevel(; alg.verbosity) do | ||
| env = deepcopy(env₀) | ||
| log = MPSKit.IterLog("BP") | ||
| ϵ = Inf | ||
| @infov 1 loginit!(log, ϵ) | ||
| for iter in 1:(alg.maxiter) | ||
| env′ = bp_iteration(network, env, alg) | ||
| ϵ = oftype(ϵ, tr_distance(env, env′)) | ||
| env = env′ | ||
|
|
||
| if ϵ <= alg.tol && iter >= alg.miniter | ||
| @infov 2 logfinish!(log, iter, ϵ) | ||
| break | ||
| end | ||
| if iter ≥ alg.maxiter | ||
| @warnv 1 logcancel!(log, iter, ϵ) | ||
| else | ||
| @infov 3 logiter!(log, iter, ϵ) | ||
| end | ||
| end | ||
|
|
||
| return env, ϵ | ||
| end | ||
| end | ||
| function leading_boundary(env₀::BPEnv, state, args...) | ||
| return leading_boundary(env₀, InfiniteSquareNetwork(state), args...) | ||
| end | ||
|
|
||
| """ | ||
| One iteration to update the BP environment. | ||
| """ | ||
| function bp_iteration(network::InfiniteSquareNetwork, env::BPEnv, alg::BeliefPropagation) | ||
| messages = map(eachindex(env)) do I | ||
| M = update_message(I, network, env) | ||
| normalize!(M) | ||
| alg.project_hermitian && (M = project_hermitian!!(M)) | ||
| return M | ||
| end | ||
| return BPEnv(messages) | ||
| end | ||
|
|
||
| """ | ||
| Update the BP message in `env.messages[I]`. | ||
| """ | ||
| function update_message(I::CartesianIndex{3}, network::InfiniteSquareNetwork, env::BPEnv) | ||
| dir, row, col = Tuple(I) | ||
| (1 <= dir <= 4) || throw(ArgumentError("Invalid direction $dir")) | ||
|
|
||
| A = network[row, col] | ||
| dir == SOUTH || (M_north = env[NORTH, _prev(row, end), col]) | ||
| dir == WEST || (M_east = env[EAST, row, _next(col, end)]) | ||
| dir == NORTH || (M_south = env[SOUTH, _next(row, end), col]) | ||
| dir == EAST || (M_west = env[WEST, row, _prev(col, end)]) | ||
|
|
||
| return if dir == NORTH | ||
| contract_north_message(A, M_west, M_north, M_east) | ||
| elseif dir == EAST | ||
| contract_east_message(A, M_north, M_east, M_south) | ||
| elseif dir == SOUTH | ||
| contract_south_message(A, M_east, M_south, M_west) | ||
| else # dir == WEST | ||
| contract_west_message(A, M_south, M_west, M_north) | ||
| end | ||
| end | ||
|
|
||
| function tr_distance(A::BPEnv, B::BPEnv) | ||
| return sum(zip(A.messages, B.messages)) do (a, b) | ||
| return trnorm(add(a, b, -inv(tr(b)), inv(tr(a)))) | ||
| end / length(A.messages) | ||
| end | ||
|
|
||
| function trnorm(M::AbstractTensorMap, p::Real = 1) | ||
| return TensorKit._norm(svdvals(M), p, zero(real(scalartype(M)))) | ||
| end | ||
| function trnorm!(M::AbstractTensorMap, p::Real = 1) | ||
| return TensorKit._norm(svdvals!(M), p, zero(real(scalartype(M)))) | ||
| end | ||
|
|
||
| project_hermitian!!(t) = add(t, t', 1 / 2, 1 / 2) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| """ | ||
| struct BPGauge | ||
|
|
||
| Algorithm for gauging PEPS with belief propagation fixed point messages. | ||
| """ | ||
| @kwdef struct BPGauge | ||
| # TODO: add options | ||
| end | ||
|
|
||
| """ | ||
| $(SIGNATURES) | ||
|
|
||
| Fix the gauge of `psi` using fixed point environment `env` of belief propagation. | ||
| """ | ||
| function gauge_fix(psi::InfinitePEPS, alg::BPGauge, env::BPEnv) | ||
| psi′ = copy(psi) | ||
| XXinv = map(eachcoordinate(psi, 1:2)) do I | ||
| _, X, Xinv = _bp_gauge_fix!(CartesianIndex(I), psi′, env) | ||
| return X, Xinv | ||
| end | ||
| return psi′, XXinv | ||
| end | ||
|
|
||
| function _sqrt_bp_messages(I::CartesianIndex{3}, env::BPEnv) | ||
| dir, row, col = Tuple(I) | ||
| @assert dir == NORTH || dir == EAST | ||
| M12 = env[dir, dir == NORTH ? _prev(row, end) : row, dir == EAST ? _next(col, end) : col] | ||
| sqrtM12, isqrtM12 = sqrt_invsqrt(twist(M12, 1)) | ||
|
Member
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. This twist is used to cancel the twist introduced because of the axis order choice |
||
| M21 = env[dir + 2, row, col] | ||
| sqrtM21, isqrtM21 = sqrt_invsqrt(M21) | ||
| return sqrtM12, isqrtM12, sqrtM21, isqrtM21 | ||
| end | ||
|
|
||
| """ | ||
| _bp_gauge_fix!(I, psi::InfinitePEPS, env::BPEnv) -> psi, X, X⁻¹ | ||
|
|
||
| For the bond at direction `I[1]` (which can be `NORTH` or `EAST`) | ||
| from site `I[2], I[3]`, we identify the following gauge matrices, | ||
| along the canonical direction of the PEPS arrows (`SOUTH ← NORTH` or `WEST ← EAST`): | ||
|
|
||
| ```math | ||
| I = √M₁₂⁻¹ √M₁₂ √M₂₁ √M₂₁⁻¹ | ||
| = √M₁₂⁻¹ (U Λ Vᴴ) √M₂₁⁻¹ | ||
| = (√M₁₂⁻¹ U √Λ) (√Λ Vᴴ √M₂₁⁻¹) | ||
| = X X⁻¹ | ||
| ``` | ||
|
|
||
| Which are then used to update the gauge of `psi`. Thus, by convention `X` is attached to the `SOUTH`/`WEST` directions | ||
| and `X⁻¹` is attached to the `NORTH`/`EAST` directions. | ||
| """ | ||
| function _bp_gauge_fix!(I::CartesianIndex{3}, psi::InfinitePEPS, env::BPEnv) | ||
| dir, row, col = Tuple(I) | ||
| @assert dir == NORTH || dir == EAST | ||
|
|
||
| sqrtM12, isqrtM12, sqrtM21, isqrtM21 = _sqrt_bp_messages(I, env) | ||
| U, Λ, Vᴴ = svd_compact!(sqrtM12 * sqrtM21) | ||
| sqrtΛ = sdiag_pow(Λ, 1 / 2) | ||
| X = isqrtM12 * U * sqrtΛ | ||
| invX = sqrtΛ * Vᴴ * isqrtM21 | ||
| if isdual(space(sqrtM12, 1)) | ||
| X, invX = twist(flip(X, 2), 1), flip(invX, 1) | ||
|
Member
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. This twist is used to cancel the twist introduced by
Member
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. (Will we one day deal with sectors that cannot be flipped?) |
||
| end | ||
| if dir == NORTH | ||
| psi[row, col] = absorb_north_message(psi[row, col], X) | ||
| psi[_prev(row, end), col] = absorb_south_message(psi[_prev(row, end), col], invX) | ||
| elseif dir == EAST | ||
| psi[row, col] = absorb_east_message(psi[row, col], X) | ||
| psi[row, _next(col, end)] = absorb_west_message(psi[row, _next(col, end)], invX) | ||
| end | ||
| return psi, X, invX | ||
| end | ||
|
|
||
| """ | ||
| SUWeight(env::BPEnv) | ||
|
|
||
| Construct `SUWeight` from belief propagation fixed point environment `env`. | ||
| """ | ||
| function SUWeight(env::BPEnv) | ||
| wts = map(Iterators.product(1:2, axes(env, 2), axes(env, 3))) do (dir′, row, col) | ||
| I = CartesianIndex(mod1(dir′ + 1, 2), row, col) | ||
| sqrtM12, _, sqrtM21, _ = _sqrt_bp_messages(I, env) | ||
| Λ = svd_vals!(sqrtM12 * sqrtM21) | ||
| return isdual(space(sqrtM12, 1)) ? _fliptwist_s(Λ) : Λ | ||
| end | ||
| return SUWeight(wts) | ||
| end | ||
|
|
||
| """ | ||
| BPEnv(wts::SUWeight) | ||
|
|
||
| Convert fixed point weights `wts` of trivial simple update | ||
| to a belief propagation environment. | ||
| """ | ||
| function BPEnv(wts::SUWeight) | ||
| messages = map(Iterators.product(1:4, axes(wts, 2), axes(wts, 3))) do (d, r, c) | ||
| wt = if d == NORTH | ||
| twist(wts[2, _next(r, end), c], 1) | ||
| elseif d == EAST | ||
| twist(wts[1, r, _prev(c, end)], 1) | ||
| elseif d == SOUTH | ||
| copy(wts[2, r, c]) | ||
| else # WEST | ||
| copy(wts[1, r, c]) | ||
| end | ||
| return TensorMap(wt) | ||
| end | ||
| return BPEnv(messages) | ||
| end | ||
|
|
||
| function sqrt_invsqrt(A::PEPSMessage) | ||
| if isposdef(A) | ||
| D, V = eigh_full(A) | ||
| sqrtA = V * sdiag_pow(D, 1 / 2) * V' | ||
| isqrtA = V * sdiag_pow(D, -1 / 2) * V' | ||
|
Comment on lines
+112
to
+114
Member
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. Here we have an unfortunate situation with fermions: because of the axis order
Member
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. Even for bosons, if the BPEnv is initialized with
Member
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. BTW, because of the kwarg
Member
Author
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. Sorry, that kwarg should have been For the remainder, I indeed messed up: the property to check is positive definiteness (which implies hermitian), and hermitian by itself isn't enough. However, for the fermions I would like to investigate this more closely. Looking at eq (124) in the fermionic tensor network methods paper, we can investigate the special case of BP where the messages are the identity (e.g. in a tree or MPS where the tensors have been properly gauged), and we see that indeed we expect negative values in the parity-odd sector, so that seems correct. Thinking about that a bit more, I think this is effectively what we were doing by having the other index order for the messages as well, since for these (1, 1) tensors a permutation and a twist + planar transpose should effectively be the same thing. Concretely, what I'm suggesting here is to explicitly factorize the message into a twist + the message, thereto adding a twist in the bp update contractions, as well as in the expectation value contractions, which will then make everything positive definite again. I think a good test case could actually be a product state PEPS which has as virtual spaces Does any of this make sense, or is this starting to feel like too much? Feel free to disagree with me here, I think most of my reasoning here comes from the intuition we obtained from that paper, which I'm trying to couple back to, but alternative viewpoints are definitely also appreciated!
Member
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 have made this change. Glad that's the way to go.
This is a good idea. The twists I added may actually be implicitly doing this. |
||
| else | ||
| D, V = eig_full(A) | ||
| V⁻¹ = inv(V) | ||
| sqrtA = V * sdiag_pow(D, 1 / 2) * V⁻¹ | ||
| isqrtA = V * sdiag_pow(D, -1 / 2) * V⁻¹ | ||
| if scalartype(A) <: Real | ||
| # TODO: is this valid? | ||
| sqrtA = real(sqrtA) | ||
| isqrtA = real(isqrtA) | ||
| end | ||
| end | ||
| return sqrtA, isqrtA | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.