Skip to content

Commit 1124602

Browse files
Adapt simple update to DiagonalTensorMap (#124)
1 parent a142e4e commit 1124602

File tree

8 files changed

+188
-58
lines changed

8 files changed

+188
-58
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Test
2+
using Printf
3+
using Random
4+
import Statistics: mean
5+
using TensorKit
6+
using PEPSKit
7+
8+
module MeasureHeis
9+
10+
export measure_heis
11+
12+
using TensorKit
13+
import MPSKitModels: S_x, S_y, S_z, S_exchange
14+
using PEPSKit
15+
16+
"""
17+
Measure magnetization on each site
18+
"""
19+
function cal_mags(peps::InfinitePEPS, envs::CTMRGEnv)
20+
N1, N2 = size(peps)
21+
lattice = collect(space(t, 1) for t in peps.A)
22+
# detect symmetry on physical axis
23+
symm = sectortype(space(peps.A[1, 1]))
24+
if symm == Trivial
25+
Sas = real.([S_x(symm), im * S_y(symm), S_z(symm)])
26+
elseif symm == U1Irrep
27+
# only Sz preserves <Sz>
28+
Sas = real.([S_z(symm)])
29+
else
30+
throw(ArgumentError("Unrecognized symmetry on physical axis"))
31+
end
32+
return [
33+
collect(
34+
expectation_value(
35+
peps, LocalOperator(lattice, (CartesianIndex(r, c),) => Sa), envs
36+
) for (r, c) in Iterators.product(1:N1, 1:N2)
37+
) for Sa in Sas
38+
]
39+
end
40+
41+
"""
42+
Measure physical quantities for Heisenberg model
43+
"""
44+
function measure_heis(peps::InfinitePEPS, H::LocalOperator, envs::CTMRGEnv)
45+
results = Dict{String,Any}()
46+
N1, N2 = size(peps)
47+
results["e_site"] = costfun(peps, envs, H) / (N1 * N2)
48+
results["mag"] = cal_mags(peps, envs)
49+
results["mag_norm"] = collect(
50+
norm([mags[r, c] for mags in results["mag"]]) for
51+
(r, c) in Iterators.product(1:N1, 1:N2)
52+
)
53+
return results
54+
end
55+
56+
end
57+
58+
import .MeasureHeis: measure_heis
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
include("heis_tools.jl")
2+
3+
# benchmark data is from Phys. Rev. B 94, 035133 (2016)
4+
Dbond, χenv, symm = 4, 16, Trivial
5+
trscheme_peps = truncerr(1e-10) & truncdim(Dbond)
6+
trscheme_envs = truncerr(1e-10) & truncdim(χenv)
7+
N1, N2 = 2, 2
8+
# Heisenberg model Hamiltonian
9+
# (already only includes nearest neighbor terms)
10+
ham = heisenberg_XYZ(ComplexF64, symm, InfiniteSquare(N1, N2); Jx=1.0, Jy=1.0, Jz=1.0)
11+
# convert to real tensors
12+
ham = LocalOperator(ham.lattice, Tuple(ind => real(op) for (ind, op) in ham.terms)...)
13+
14+
# random initialization of 2x2 iPEPS with weights and CTMRGEnv (using real numbers)
15+
if symm == Trivial
16+
Pspace =^2
17+
Vspace =^Dbond
18+
Espace =^χenv
19+
elseif symm == U1Irrep
20+
Pspace = ℂ[U1Irrep](1//2 => 1, -1//2 => 1)
21+
Vspace = ℂ[U1Irrep](0 => Dbond ÷ 2, 1//2 => Dbond ÷ 4, -1//2 => Dbond ÷ 4)
22+
Espace = ℂ[U1Irrep](0 => χenv ÷ 2, 1//2 => χenv ÷ 4, -1//2 => χenv ÷ 4)
23+
else
24+
error("Not implemented")
25+
end
26+
Random.seed!(0)
27+
peps = InfiniteWeightPEPS(rand, Float64, Pspace, Vspace; unitcell=(N1, N2))
28+
# normalize vertex tensors
29+
for ind in CartesianIndices(peps.vertices)
30+
peps.vertices[ind] /= norm(peps.vertices[ind], Inf)
31+
end
32+
33+
# simple update
34+
dts = [1e-2, 1e-3, 4e-4]
35+
tols = [1e-6, 1e-8, 1e-8]
36+
maxiter = 10000
37+
for (dt, tol) in zip(dts, tols)
38+
alg = SimpleUpdate(dt, tol, maxiter, trscheme_peps)
39+
result = simpleupdate(peps, ham, alg; bipartite=true)
40+
global peps = result[1]
41+
end
42+
# measure physical quantities with CTMRG
43+
peps_ = InfinitePEPS(peps)
44+
envs = CTMRGEnv(rand, Float64, peps_, Espace)
45+
ctm_alg = SequentialCTMRG(; tol=1e-10, verbosity=2, trscheme=trscheme_envs)
46+
envs = leading_boundary(envs, peps_, ctm_alg)
47+
meas = measure_heis(peps_, ham, envs)
48+
display(meas)
49+
@info @sprintf("Energy = %.8f\n", meas["e_site"])
50+
@info @sprintf("Staggered magnetization = %.8f\n", mean(meas["mag_norm"]))
51+
@test isapprox(meas["e_site"], -0.6675; atol=1e-3)
52+
@test isapprox(mean(meas["mag_norm"]), 0.3767; atol=1e-3)

src/PEPSKit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ include("algorithms/ctmrg/simultaneous.jl")
4949
include("algorithms/ctmrg/sequential.jl")
5050
include("algorithms/ctmrg/gaugefix.jl")
5151

52-
include("algorithms/time_evolution/gatetools.jl")
52+
include("algorithms/time_evolution/evoltools.jl")
5353
include("algorithms/time_evolution/simpleupdate.jl")
5454

5555
include("algorithms/toolbox.jl")

src/algorithms/ctmrg/sparse_environments.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ function TensorKit.codomain(env::HalfInfiniteEnv)
229229
end
230230

231231
function random_start_vector(env::HalfInfiniteEnv)
232-
return Tensor(randn, domain(env))
232+
return randn(domain(env))
233233
end
234234

235235
# --------------------------------
@@ -377,5 +377,5 @@ function TensorKit.codomain(env::FullInfiniteEnv)
377377
end
378378

379379
function random_start_vector(env::FullInfiniteEnv)
380-
return Tensor(randn, domain(env))
380+
return randn(domain(env))
381381
end

src/algorithms/time_evolution/gatetools.jl renamed to src/algorithms/time_evolution/evoltools.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,20 @@ function get_gateterm(gate::LocalOperator, bond::NTuple{2,CartesianIndex{2}})
4949
return gate.terms[label[1]].second
5050
end
5151
end
52+
53+
const _axlabels = Set("trbl")
54+
55+
"""
56+
Absorb environment weights into tensor `t` in position `[row, col]` in an iPEPS with `weights`.
57+
A full weight is absorbed into axes of `t` on the boundary (specified by `open_axs`).
58+
But on internal bonds, square root of weights are absorbed.
59+
"""
60+
function _absorb_weight(
61+
t::PEPSTensor, row::Int, col::Int, open_axs::String, weights::SUWeight
62+
)
63+
@assert all(c -> c in _axlabels, open_axs)
64+
for (ax, ch) in zip(2:5, "trbl")
65+
t = absorb_weight(t, row, col, ax, weights; sqrtwt=!(ch in open_axs))
66+
end
67+
return t
68+
end

src/algorithms/time_evolution/simpleupdate.jl

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,11 @@ function _su_bondx!(
4242
) where {T<:Number,S<:ElementarySpace}
4343
Nr, Nc = size(peps)
4444
@assert 1 <= row <= Nr && 1 <= col <= Nc
45-
row2, col2 = row, _next(col, Nc)
46-
T1, T2 = peps.vertices[row, col], peps.vertices[row2, col2]
45+
cp1 = _next(col, Nc)
4746
# absorb environment weights
48-
for ax in (2, 4, 5)
49-
T1 = absorb_weight(T1, row, col, ax, peps.weights)
50-
end
51-
for ax in (2, 3, 4)
52-
T2 = absorb_weight(T2, row2, col2, ax, peps.weights)
53-
end
54-
# absorb bond weight
55-
T1 = absorb_weight(T1, row, col, 3, peps.weights; sqrtwt=true)
56-
T2 = absorb_weight(T2, row2, col2, 5, peps.weights; sqrtwt=true)
47+
T1, T2 = peps.vertices[row, col], peps.vertices[row, cp1]
48+
T1 = _absorb_weight(T1, row, col, "tbl", peps.weights)
49+
T2 = _absorb_weight(T2, row, cp1, "trb", peps.weights)
5750
#= QR and LQ decomposition
5851
5952
2 1 1 2
@@ -72,17 +65,18 @@ function _su_bondx!(
7265
bL, Y = rightorth(T2, ((5, 1), (2, 3, 4)); alg=LQpos())
7366
#= apply gate
7467
75-
-2 -3
68+
-2 -3
7669
↑ ↑
7770
|----gate---|
7871
↑ ↑
7972
1 2
8073
↑ ↑
8174
-1← aR -← 3 -← bL ← -4
8275
=#
83-
@tensor tmp[-1 -2; -3 -4] := gate[-2, -3, 1, 2] * aR[-1, 1, 3] * bL[3, 2, -4]
84-
# SVD
85-
aR, s, bL, ϵ = tsvd!(tmp; trunc=truncation_scheme(alg, space(T1, 3)))
76+
@tensor tmp[-1 -2; -3 -4] := gate[-2 -3; 1 2] * aR[-1 1 3] * bL[3 2 -4]
77+
aR, s, bL, ϵ = tsvd!(
78+
tmp; trunc=truncation_scheme(alg, space(T1, 3)), alg=TensorKit.SVD()
79+
)
8680
#=
8781
-2 -1 -1 -2
8882
| ↗ ↗ |
@@ -97,11 +91,11 @@ function _su_bondx!(
9791
T1 = absorb_weight(T1, row, col, ax, peps.weights; invwt=true)
9892
end
9993
for ax in (2, 3, 4)
100-
T2 = absorb_weight(T2, row2, col2, ax, peps.weights; invwt=true)
94+
T2 = absorb_weight(T2, row, cp1, ax, peps.weights; invwt=true)
10195
end
10296
# update tensor dict and weight on current bond
10397
# (max element of weight is normalized to 1)
104-
peps.vertices[row, col], peps.vertices[row2, col2] = T1, T2
98+
peps.vertices[row, col], peps.vertices[row, cp1] = T1, T2
10599
peps.weights[1, row, col] = s / norm(s, Inf)
106100
return ϵ
107101
end
@@ -181,17 +175,17 @@ function simpleupdate(
181175
check_int::Int=500,
182176
)
183177
time_start = time()
184-
N1, N2 = size(peps)
178+
Nr, Nc = size(peps)
185179
if bipartite
186-
@assert N1 == N2 == 2
180+
@assert Nr == Nc == 2
187181
end
188182
# exponentiating the 2-site Hamiltonian gate
189183
gate = get_gate(alg.dt, ham)
190184
wtdiff = 1.0
191185
wts0 = deepcopy(peps.weights)
192186
for count in 1:(alg.maxiter)
193187
time0 = time()
194-
peps = su_iter(gate, peps, alg; bipartite=bipartite)
188+
peps = su_iter(gate, peps, alg; bipartite)
195189
wtdiff = compare_weights(peps.weights, wts0)
196190
converge = (wtdiff < alg.tol)
197191
cancel = (count == alg.maxiter)
@@ -210,9 +204,7 @@ function simpleupdate(
210204
)
211205
cancel ? (@warn message) : (@info message)
212206
end
213-
if converge || cancel
214-
break
215-
end
207+
converge && break
216208
end
217209
return peps, wtdiff
218210
end

src/states/infiniteweightpeps.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22
"""
3-
const PEPSWeight{S}
3+
const PEPSWeight
44
55
Default type for PEPS bond weights with 2 virtual indices, conventionally ordered as: ``wt : WS ← EN``.
66
`WS`, `EN` denote the west/south, east/north spaces for x/y-weights on the square lattice, respectively.
77
"""
8-
const PEPSWeight{T,S} = AbstractTensorMap{T,S,1,1} where {T<:Number,S<:ElementarySpace}
8+
const PEPSWeight{T,S} = AbstractTensorMap{T,S,1,1}
99

1010
"""
1111
struct SUWeight{E<:PEPSWeight}
@@ -108,9 +108,10 @@ function InfiniteWeightPEPS(
108108
) where {S<:ElementarySpace}
109109
vertices = InfinitePEPS(f, T, Pspace, Nspace, Espace; unitcell=unitcell).A
110110
Nr, Nc = unitcell
111-
weights = collect(
112-
id(d == 1 ? Espace : Nspace) for (d, r, c) in Iterators.product(1:2, 1:Nr, 1:Nc)
113-
)
111+
weights = map(Iterators.product(1:2, 1:Nr, 1:Nc)) do (d, r, c)
112+
V = (d == 1 ? Espace : Nspace)
113+
DiagonalTensorMap(ones(reduceddim(V)), V)
114+
end
114115
return InfiniteWeightPEPS(vertices, SUWeight(weights))
115116
end
116117

src/utility/util.jl

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,53 +45,63 @@ function _elementwise_mult(a₁::AbstractTensorMap, a₂::AbstractTensorMap)
4545
return dst
4646
end
4747

48-
_safe_pow(a, pow, tol) = (pow < 0 && abs(a) < tol) ? zero(a) : a^pow
48+
_safe_pow(a::Real, pow::Real, tol::Real) = (pow < 0 && abs(a) < tol) ? zero(a) : a^pow
4949

5050
"""
51-
sdiag_pow(S::AbstractTensorMap, pow::Real; tol::Real=eps(scalartype(S))^(3 / 4))
51+
sdiag_pow(s, pow::Real; tol::Real=eps(scalartype(s))^(3 / 4))
5252
53-
Compute `S^pow` for diagonal matrices `S`.
53+
Compute `s^pow` for a diagonal matrix `s`.
5454
"""
55-
function sdiag_pow(S::AbstractTensorMap, pow::Real; tol::Real=eps(scalartype(S))^(3 / 4))
56-
tol *= norm(S, Inf) # Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable)
57-
Spow = similar(S)
58-
for (k, b) in blocks(S)
55+
function sdiag_pow(s::DiagonalTensorMap, pow::Real; tol::Real=eps(scalartype(s))^(3 / 4))
56+
# Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable)
57+
tol *= norm(s, Inf)
58+
spow = DiagonalTensorMap(_safe_pow.(s.data, pow, tol), space(s, 1))
59+
return spow
60+
end
61+
function sdiag_pow(
62+
s::AbstractTensorMap{T,S,1,1}, pow::Real; tol::Real=eps(scalartype(s))^(3 / 4)
63+
) where {T,S}
64+
# Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable)
65+
tol *= norm(s, Inf)
66+
spow = similar(s)
67+
for (k, b) in blocks(s)
5968
copyto!(
60-
block(Spow, k), LinearAlgebra.diagm(_safe_pow.(LinearAlgebra.diag(b), pow, tol))
69+
block(spow, k), LinearAlgebra.diagm(_safe_pow.(LinearAlgebra.diag(b), pow, tol))
6170
)
6271
end
63-
return Spow
64-
end
65-
66-
"""
67-
absorb_s(u::AbstractTensorMap, s::AbstractTensorMap, vh::AbstractTensorMap)
68-
69-
Given `tsvd` result `u`, `s` and `vh`, absorb singular values `s` into `u` and `vh` by:
70-
```
71-
u -> u * sqrt(s), vh -> sqrt(s) * vh
72-
```
73-
"""
74-
function absorb_s(u::AbstractTensorMap, s::AbstractTensorMap, vh::AbstractTensorMap)
75-
sqrt_s = sdiag_pow(s, 0.5)
76-
return u * sqrt_s, sqrt_s * vh
72+
return spow
7773
end
7874

7975
function ChainRulesCore.rrule(
8076
::typeof(sdiag_pow),
81-
S::AbstractTensorMap,
77+
s::AbstractTensorMap,
8278
pow::Real;
83-
tol::Real=eps(scalartype(S))^(3 / 4),
79+
tol::Real=eps(scalartype(s))^(3 / 4),
8480
)
85-
tol *= norm(S, Inf)
86-
spow = sdiag_pow(S, pow; tol)
87-
spow_minus1_conj = scale!(sdiag_pow(S', pow - 1; tol), pow)
81+
tol *= norm(s, Inf)
82+
spow = sdiag_pow(s, pow; tol)
83+
spow_minus1_conj = scale!(sdiag_pow(s', pow - 1; tol), pow)
8884
function sdiag_pow_pullback(c̄_)
8985
= unthunk(c̄_)
9086
return (ChainRulesCore.NoTangent(), _elementwise_mult(c̄, spow_minus1_conj))
9187
end
9288
return spow, sdiag_pow_pullback
9389
end
9490

91+
"""
92+
absorb_s(u::AbstractTensorMap, s::DiagonalTensorMap, vh::AbstractTensorMap)
93+
94+
Given `tsvd` result `u`, `s` and `vh`, absorb singular values `s` into `u` and `vh` by:
95+
```
96+
u -> u * sqrt(s), vh -> sqrt(s) * vh
97+
```
98+
"""
99+
function absorb_s(u::AbstractTensorMap, s::DiagonalTensorMap, vh::AbstractTensorMap)
100+
@assert !isdual(space(s, 1))
101+
sqrt_s = sdiag_pow(s, 0.5)
102+
return u * sqrt_s, sqrt_s * vh
103+
end
104+
95105
# Check whether diagonals contain degenerate values up to absolute or relative tolerance
96106
function is_degenerate_spectrum(
97107
S; atol::Real=0, rtol::Real=atol > 0 ? 0 : sqrt(eps(scalartype(S)))

0 commit comments

Comments
 (0)