Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
cf42e16
Add `BPEnv`
lkdvos Jun 13, 2025
9765a5c
Add `BPEnv` constructors
lkdvos Jun 13, 2025
6a17656
Add BP contractions
lkdvos Jun 13, 2025
ccae695
Add BP VectorInterface support
lkdvos Jun 13, 2025
247aa59
Add BP nearest neighbor expectation values
lkdvos Jun 14, 2025
c00f847
Add bp normalization
lkdvos Jun 14, 2025
8b91621
Move contractions to separate file
lkdvos Jun 14, 2025
96c3535
Add BP fixed point
lkdvos Jun 14, 2025
48942df
Add `gauge_fix` function
lkdvos Jun 14, 2025
5f668b3
Remove repeated `import VectorInterface`
Yue-Zhengyuan Jun 18, 2025
3243ec8
Format
kshyatt Nov 18, 2025
baaa600
Kill ProductSpaceLike
kshyatt Nov 18, 2025
9b41f64
Try to add some simple tests
kshyatt Nov 18, 2025
32a8ecc
Add BP to the test workflow too and format
kshyatt Nov 18, 2025
9c47930
Death to VectorInterface
kshyatt Nov 18, 2025
60939e7
Some 0.7 updates
kshyatt Nov 18, 2025
d03e1f0
Format
kshyatt Nov 18, 2025
74b987e
Absorb sqrt(weights) into vertex tensors [skip ci]
Yue-Zhengyuan Nov 19, 2025
88824b6
Add docstring for BPEnv [skip ci]
Yue-Zhengyuan Nov 19, 2025
b216bc5
export BPEnv, BeliefPropagation
Yue-Zhengyuan Nov 20, 2025
feb9505
Fix bond mismatch and enforce Hermiticity in `bp_iteration`
Yue-Zhengyuan Nov 20, 2025
8cb1bb4
Add test to compare BP and SU fixed point
Yue-Zhengyuan Nov 20, 2025
b15a5a7
Fix BP gauge fixing
Yue-Zhengyuan Nov 20, 2025
9a94435
Fix formatting
Yue-Zhengyuan Nov 20, 2025
14675d4
Preserve virtual arrows with flip_svd
Yue-Zhengyuan Nov 22, 2025
1f5997b
Implement rotation of BPEnv
Yue-Zhengyuan Nov 22, 2025
8f3dbff
Merge remote-tracking branch 'upstream/master' into bp
Yue-Zhengyuan Nov 22, 2025
e5d17a8
Add BPEnv to CTMRGEnv conversion [skip ci]
Yue-Zhengyuan Nov 22, 2025
d45065b
Separate bp_fixedpoint and gauge_fix
Yue-Zhengyuan Nov 23, 2025
63e7884
Rename test file
Yue-Zhengyuan Nov 23, 2025
5f168f0
Add docstring for BPEnv constructors
Yue-Zhengyuan Nov 23, 2025
f56343c
Add Base.size
Yue-Zhengyuan Nov 23, 2025
d989a4c
Add BP expectation value test
Yue-Zhengyuan Nov 23, 2025
6c6a47c
Fix unitcell test
Yue-Zhengyuan Nov 23, 2025
21ce542
Merge branch 'master' into bp
lkdvos Dec 4, 2025
8480184
add some type properties, simplify CTMRGEnv converter
lkdvos Dec 4, 2025
c8a950b
make hermiticity an algorithm parameter
lkdvos Dec 4, 2025
d956d93
improve docstrings, add miniter
lkdvos Dec 4, 2025
0742297
normalize BP error criterion
lkdvos Dec 4, 2025
1515491
small fixes
lkdvos Dec 4, 2025
a27dca1
refactor gauge fixing
lkdvos Dec 4, 2025
e8cc860
improve type stability
lkdvos Dec 4, 2025
e7b2b26
initialize PEPS messages with identity
lkdvos Dec 4, 2025
0979971
return gauges
lkdvos Dec 4, 2025
a42e715
small fixes
lkdvos Dec 4, 2025
826ef10
Merge remote-tracking branch 'upstream/master' into bp
Yue-Zhengyuan Jan 6, 2026
590bdb4
Adapt to removal of flip_svd
Yue-Zhengyuan Jan 6, 2026
e4554f2
Separate BPGauge from BP
Yue-Zhengyuan Jan 7, 2026
3635a4c
Bring back `SUWeight(::BPEnv)`
Yue-Zhengyuan Jan 7, 2026
bba8163
Move `random_dual!` to utils; add fermion gauge fix test
Yue-Zhengyuan Jan 7, 2026
ea866df
Refactor BPEnv constructor
Yue-Zhengyuan Jan 7, 2026
37adcfb
Move trivial SU gauging to src
Yue-Zhengyuan Jan 7, 2026
bd34b9b
Add `BPEnv(::SUWeight)`
Yue-Zhengyuan Jan 7, 2026
c3b49f8
Change west, south message axis order
Yue-Zhengyuan Jan 8, 2026
11daeae
Temp. remove fermion gauging test; add non-hermitian test
Yue-Zhengyuan Jan 8, 2026
32a2f8f
Automatically check Hermiticity of messages when fixing gauge
Yue-Zhengyuan Jan 8, 2026
6c239e0
Add `posdef` option for BPEnv constructors
Yue-Zhengyuan Jan 8, 2026
ae0496f
Handle posdef-ness of fermionic messages
Yue-Zhengyuan Jan 8, 2026
9fcb13b
Fix BP for fermions with non-standard virtual arrows
Yue-Zhengyuan Jan 8, 2026
0b6fc6e
Update docstrings and error messages
Yue-Zhengyuan Jan 8, 2026
412726b
Update docstrings again
Yue-Zhengyuan Jan 8, 2026
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 .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- 'lts' # minimal supported version
- '1'
group:
- bp
- types
- ctmrg
- boundarymps
Expand Down
9 changes: 9 additions & 0 deletions src/PEPSKit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ include("operators/models.jl")
include("environments/ctmrg_environments.jl")
include("environments/vumps_environments.jl")
include("environments/suweight.jl")
include("environments/bp_environments.jl")

include("algorithms/contractions/ctmrg_contractions.jl")
include("algorithms/contractions/transfer.jl")
include("algorithms/contractions/localoperator.jl")
include("algorithms/contractions/vumps_contractions.jl")
include("algorithms/contractions/bp_contractions.jl")
include("algorithms/contractions/bondenv/benv_tools.jl")
include("algorithms/contractions/bondenv/gaugefix.jl")
include("algorithms/contractions/bondenv/als_solve.jl")
Expand All @@ -78,6 +80,10 @@ include("algorithms/time_evolution/evoltools.jl")
include("algorithms/time_evolution/time_evolve.jl")
include("algorithms/time_evolution/simpleupdate.jl")
include("algorithms/time_evolution/simpleupdate3site.jl")
include("algorithms/time_evolution/gaugefix_su.jl")

include("algorithms/bp/beliefpropagation.jl")
include("algorithms/bp/gaugefix.jl")

include("algorithms/transfermatrix.jl")
include("algorithms/toolbox.jl")
Expand Down Expand Up @@ -114,6 +120,9 @@ export InfinitePartitionFunction
export InfinitePEPS, InfiniteTransferPEPS
export SUWeight
export InfinitePEPO, InfiniteTransferPEPO

export BPEnv, BeliefPropagation, BPGauge

export initialize_mps, initializePEPS
export ReflectDepth, ReflectWidth, Rotate, RotateReflect
export symmetrize!, symmetrize_retract_and_finalize!
Expand Down
111 changes: 111 additions & 0 deletions src/algorithms/bp/beliefpropagation.jl
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)
127 changes: 127 additions & 0 deletions src/algorithms/bp/gaugefix.jl
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))
Copy link
Member

Choose a reason for hiding this comment

The 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 ket <- bra for the north and east messages, in order to obtain a positive definite map.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This twist is used to cancel the twist introduced by flip when reversing the arrow direction. It can be equally applied to invX instead.

Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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 ket (top) <- bra (bottom), the eigenvalues in the parity-odd sector are negative. So, we cannot directly apply sdiag_pow.

Copy link
Member

@Yue-Zhengyuan Yue-Zhengyuan Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even for bosons, if the BPEnv is initialized with randn instead of ones, there can also be negative eigenvalues of the messages. In more extreme cases there can be complex eigenvalues. In such (non-Hermitian) cases I guess the gauge fixing should still be done with SVD instead of eigen-decomposition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, because of the kwarg tol::Real = eps(scalartype(s))^(3 / 4), sdiag_pow cannot handle complex DiagonalTensorMaps unless tol is explicitly provided.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that kwarg should have been eps(real(scalartype(s)))^(3/4).

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.
However, I think we should be able to (and probably want to!) redefine or contractions by adding this twist explicitly, which will then restore the positive definiteness of our messages.

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 Vect[FermionParity](1 => 1).
Because of the product state structure, the messages should (up to normalization) all be the identity and positive definite, no matter the unit cell.
I think that for this case, missing twists would make odd unit cell sizes not converge, and even unit cell sizes get staggering positive and negative messages (might be wrong about this though)

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!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that kwarg should have been eps(real(scalartype(s)))^(3/4)

I have made this change. Glad that's the way to go.

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.

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
Loading