Skip to content

Commit 9fe20ae

Browse files
authored
Change sector convention for excited states(#271)
This changes the meaning of the `sector` keyword argument to be more in line with what users would typically expect.
1 parent 3b9bfed commit 9fe20ae

File tree

7 files changed

+246
-55
lines changed

7 files changed

+246
-55
lines changed

src/algorithms/ED.jl

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,47 +31,51 @@ equivalent to dense eigenvectors.
3131
3232
"""
3333
function exact_diagonalization(H::FiniteMPOHamiltonian;
34-
sector=first(sectors(oneunit(physicalspace(H, 1)))),
35-
len::Int=length(H), num::Int=1, which::Symbol=:SR,
34+
sector=one(sectortype(H)),
35+
num::Int=1, which::Symbol=:SR,
3636
alg=Defaults.alg_eigsolve(; dynamic_tols=false))
37-
left = ℂ[typeof(sector)](sector => 1)
38-
right = oneunit(left)
37+
L = length(H)
38+
@assert L > 1 "FiniteMPOHamiltonian must have length > 1"
39+
middle_site = (L >> 1) + 1
3940

40-
middle_site = Int(round(len / 2))
41-
42-
Ot = eltype(H[1])
43-
44-
mpst_type = tensormaptype(spacetype(Ot), 2, 1, storagetype(Ot))
45-
mpsb_type = tensormaptype(spacetype(Ot), 1, 1, storagetype(Ot))
46-
Cs = Vector{Union{Missing,mpsb_type}}(missing, len + 1)
47-
ALs = Vector{Union{Missing,mpst_type}}(missing, len)
48-
ARs = Vector{Union{Missing,mpst_type}}(missing, len)
49-
ACs = Vector{Union{Missing,mpst_type}}(missing, len)
41+
T = storagetype(eltype(H))
42+
TA = tensormaptype(spacetype(H), 2, 1, T)
5043

44+
# fuse from left to right
45+
ALs = Vector{Union{Missing,TA}}(missing, L)
46+
left = oneunit(spacetype(H))
5147
for i in 1:(middle_site - 1)
52-
ALs[i] = isomorphism(storagetype(Ot), left * physicalspace(H, i),
53-
fuse(left * physicalspace(H, i)))
54-
left = _lastspace(ALs[i])'
48+
P = physicalspace(H, i)
49+
ALs[i] = isomorphism(T, left P fuse(left P))
50+
left = right_virtualspace(ALs[i])
5551
end
56-
for i in len:-1:(middle_site + 1)
57-
ARs[i] = _transpose_front(isomorphism(storagetype(Ot),
58-
fuse(right * physicalspace(H, i)'),
59-
right * physicalspace(H, i)'))
60-
right = _firstspace(ARs[i])
52+
53+
# fuse from right to left
54+
ARs = Vector{Union{Missing,TA}}(missing, L)
55+
right = spacetype(H)(sector => 1)
56+
for i in reverse((middle_site + 1):L)
57+
P = physicalspace(H, i)
58+
ARs[i] = _transpose_front(isomorphism(T, fuse(right P') right P'))
59+
right = left_virtualspace(ARs[i])
6160
end
62-
ACs[middle_site] = randomize!(similar(H[1][1, 1, 1, 1],
63-
left * physicalspace(H, middle_site) right))
64-
norm(ACs[middle_site]) == 0 && throw(ArgumentError("invalid sector"))
65-
normalize!(ACs[middle_site])
6661

67-
#construct the largest possible finite mps of that length
62+
# center
63+
ACs = Vector{Union{Missing,TA}}(missing, L)
64+
P = physicalspace(H, middle_site)
65+
ACs[middle_site] = rand!(similar(ALs[1], left P right))
66+
67+
TB = tensormaptype(spacetype(H), 1, 1, T)
68+
Cs = Vector{Union{Missing,TB}}(missing, L + 1)
6869
state = FiniteMPS(ALs, ARs, ACs, Cs)
6970
envs = environments(state, H)
7071

71-
#optimize the middle site. Because there is no truncation, this single site captures the entire possible hilbert space
72-
H_ac = ∂∂AC(middle_site, state, H, envs) # this linear operator is now the actual full hamiltonian!
73-
(vals, vecs, convhist) = eigsolve(H_ac, state.AC[middle_site], num, which, alg)
72+
# optimize the middle site
73+
# Because the MPS is full rank - this is equivalent to the full Hamiltonian
74+
AC₀ = state.AC[middle_site]
75+
H_ac = ∂∂AC(middle_site, state, H, envs)
76+
vals, vecs, convhist = eigsolve(H_ac, AC₀, num, which, alg)
7477

78+
# repack eigenstates
7579
state_vecs = map(vecs) do v
7680
cs = copy(state)
7781
cs.AC[middle_site] = v

src/operators/abstractmpo.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ end
261261

262262
function add_physical_charge(O::MPOTensor, charge::Sector)
263263
sectortype(O) === typeof(charge) || throw(SectorMismatch())
264-
auxspace = Vect[typeof(charge)](charge => 1)
264+
auxspace = Vect[typeof(charge)](charge => 1)'
265265
F = fuser(scalartype(O), physicalspace(O), auxspace)
266266
@plansor O_charged[-1 -2; -3 -4] := F[-2; 1 2] *
267267
O[-1 1; 4 3] *
@@ -270,16 +270,14 @@ function add_physical_charge(O::MPOTensor, charge::Sector)
270270
end
271271
function add_physical_charge(O::BraidingTensor, charge::Sector)
272272
sectortype(O) === typeof(charge) || throw(SectorMismatch())
273-
auxspace = Vect[typeof(charge)](charge => 1)
273+
auxspace = Vect[typeof(charge)](charge => 1)'
274274
V = left_virtualspace(O) fuse(physicalspace(O), auxspace)
275275
fuse(physicalspace(O), auxspace) right_virtualspace(O)
276276
return BraidingTensor{scalartype(O)}(V)
277277
end
278278
function add_physical_charge(O::AbstractBlockTensorMap{<:Any,<:Any,2,2}, charge::Sector)
279279
sectortype(O) == typeof(charge) || throw(SectorMismatch())
280-
281-
auxspace = Vect[typeof(charge)](charge => 1)
282-
280+
auxspace = Vect[typeof(charge)](charge => 1)'
283281
Odst = similar(O,
284282
left_virtualspace(O) fuse(physicalspace(O), auxspace)
285283
fuse(physicalspace(O), auxspace) right_virtualspace(O))

src/states/finitemps.jl

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,13 @@ function FiniteMPS(f, elt, Pspaces::Vector{<:Union{S,CompositeSpace{S}}},
234234

235235
Vspaces[1] = left
236236
for k in 2:N
237-
Vspaces[k] = infimum(fuse(Vspaces[k - 1], fusedPspaces[k]), maxVspaces[k - 1])
237+
Vspaces[k] = infimum(fuse(Vspaces[k - 1], fusedPspaces[k - 1]), maxVspaces[k - 1])
238238
dim(Vspaces[k]) > 0 || @warn "no fusion channels available at site $k"
239239
end
240240

241241
Vspaces[end] = right
242-
for k in N:-1:2
243-
Vspaces[k] = infimum(maxVspaces[k - 1], fuse(Vspaces[k + 1], dual(fusedPspaces[k])))
242+
for k in reverse(2:N)
243+
Vspaces[k] = infimum(Vspaces[k], fuse(Vspaces[k + 1], dual(fusedPspaces[k])))
244244
dim(Vspaces[k]) > 0 || @warn "no fusion channels available at site $k"
245245
end
246246

@@ -383,18 +383,36 @@ function TensorKit.space(ψ::FiniteMPS{<:GenericMPSTensor}, n::Integer)
383383
return ProductSpace{S}(space.(Ref(t), Base.front(Base.tail(TensorKit.allind(t)))))
384384
end
385385

386+
"""
387+
max_virtualspaces(ψ::FiniteMPS)
388+
max_virtualspaces(Ps::Vector{<:Union{S,CompositeSpace{S}}}; left=oneunit(S), right=oneunit(S))
389+
390+
Compute the maximal virtual spaces of a given finite MPS or its physical spaces.
391+
"""
392+
function max_virtualspaces(Ps::Vector{<:Union{S,CompositeSpace{S}}}; left=oneunit(S),
393+
right=oneunit(S)) where {S<:ElementarySpace}
394+
Vs = similar(Ps, length(Ps) + 1)
395+
Vs[1] = left
396+
Vs[end] = right
397+
for k in 2:length(Ps)
398+
Vs[k] = fuse(Vs[k - 1], fuse(Ps[k - 1]))
399+
end
400+
for k in reverse(2:length(Ps))
401+
Vs[k] = infimum(Vs[k], fuse(Vs[k + 1], dual(fuse(Ps[k]))))
402+
end
403+
return Vs
404+
end
405+
function max_virtualspaces::FiniteMPS)
406+
return max_virtualspaces(physicalspace(ψ); left=left_virtualspace(ψ, 1),
407+
right=right_virtualspace(ψ, length(ψ)))
408+
end
409+
386410
"""
387411
max_Ds(ψ::FiniteMPS) -> Vector{Float64}
388412
389413
Compute the dimension of the maximal virtual space at a given site.
390414
"""
391-
function max_Ds::FiniteMPS)
392-
N = length(ψ)
393-
physicaldims = dim.(space.(Ref(ψ)), 1:N)
394-
D_left = cumprod(vcat(dim(left_virtualspace(ψ, 1)), physicaldims))
395-
D_right = cumprod(vcat(dim(right_virtualspace(ψ, N)), reverse(physicaldims)))
396-
return min.(D_left, D_right)
397-
end
415+
max_Ds::FiniteMPS) = dim.(max_virtualspaces(ψ))
398416

399417
function Base.summary(io::IO, ψ::FiniteMPS)
400418
return print(io, "$(length(ψ))-site FiniteMPS ($(scalartype(ψ)), $(spacetype(ψ)))")

src/states/quasiparticle_state.jl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ function LeftGaugedQP(datfun, left_gs, right_gs=left_gs;
4141
Xs = map(enumerate(VLs)) do (loc, vl)
4242
x = similar(vl,
4343
right_virtualspace(vl)
44-
excitation_space' right_virtualspace(right_gs, loc))
44+
excitation_space right_virtualspace(right_gs, loc))
4545
fill_data!(x, datfun)
4646
return x
4747
end
48+
sum(dim, Xs) == 0 && @warn "LeftGaugedQP: No possible fusion channels"
4849
left_gs isa InfiniteMPS ||
4950
momentum == zero(momentum) ||
5051
@warn "momentum is ignored for finite quasiparticles"
@@ -63,13 +64,15 @@ end
6364

6465
function RightGaugedQP(datfun, left_gs, right_gs=left_gs;
6566
sector=one(sectortype(left_gs)), momentum=0.0)
66-
#find the left null spaces for the TNS
67+
# find the left null spaces for the TNS
6768
excitation_space = Vect[typeof(sector)](sector => 1)
68-
VRs = [adjoint(leftnull(adjoint(v))) for v in _transpose_tail.(right_gs.AR)]
69-
Xs = [TensorMap{scalartype(left_gs)}(undef, left_virtualspace(right_gs, loc)',
70-
excitation_space' * _firstspace(VRs[loc]))
71-
for loc in 1:length(left_gs)]
72-
fill_data!.(Xs, datfun)
69+
VRs = convert(Vector, map(rightnull! _transpose_tail, right_gs.AR))
70+
Xs = map(enumerate(VRs)) do (i, vr)
71+
x = similar(vr,
72+
left_virtualspace(left_gs, i)'
73+
excitation_space _firstspace(vr))
74+
return fill_data!(x, datfun)
75+
end
7376
left_gs isa InfiniteMPS ||
7477
momentum == zero(momentum) ||
7578
@warn "momentum is ignored for finite quasiparticles"
@@ -84,10 +87,10 @@ function Base.similar(v::RightGaugedQP, ::Type{T}=scalartype(v)) where {T<:Numbe
8487
return RightGaugedQP(v.left_gs, v.right_gs, similar.(v.Xs, T), v.VRs, v.momentum)
8588
end
8689

87-
Base.getindex(v::LeftGaugedQP, i::Int) = v.VLs[mod1(i, end)] * v.Xs[mod1(i, end)];
90+
Base.getindex(v::LeftGaugedQP, i::Int) = v.VLs[mod1(i, end)] * v.Xs[mod1(i, end)]
8891
function Base.getindex(v::RightGaugedQP, i::Int)
8992
@plansor t[-1 -2; -3 -4] := v.Xs[mod1(i, end)][-1; -3 1] * v.VRs[mod1(i, end)][1; -4 -2]
90-
end;
93+
end
9194

9295
function Base.setindex!(v::LeftGaugedQP, B, i::Int)
9396
v.Xs[mod1(i, end)] = v.VLs[mod1(i, end)]' * B

test/algorithms.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ using MPSKit
1111
using MPSKit: fuse_mul_mpo
1212
using TensorKit
1313
using TensorKit:
14+
using LinearAlgebra: eigvals
1415

1516
verbosity_full = 5
1617
verbosity_conv = 1
@@ -917,4 +918,50 @@ end
917918
@test Z_tdvp Z_dense_2 atol = 1e-2
918919
end
919920

921+
@testset "Sector conventions" begin
922+
L = 4
923+
H = TestSetup.XY_model(U1Irrep; L)
924+
925+
H_dense = convert(TensorMap, H)
926+
vals_dense = eigvals(H_dense)
927+
928+
tol = 1e-18 # tolerance required to separate degenerate eigenvalues
929+
alg = MPSKit.Defaults.alg_eigsolve(; dynamic_tols=false, tol)
930+
931+
maxVspaces = MPSKit.max_virtualspaces(physicalspace(H))
932+
gs, = find_groundstate(FiniteMPS(physicalspace(H), maxVspaces[2:(end - 1)]), H;
933+
verbosity=0)
934+
E₀ = expectation_value(gs, H)
935+
@test E₀ first(vals_dense[one(U1Irrep)])
936+
937+
for (sector, vals) in vals_dense
938+
# ED tests
939+
num = length(vals)
940+
E₀s, ψ₀s, info = exact_diagonalization(H; num, sector, alg)
941+
@test E₀s[1:num] vals[1:num]
942+
# this is a trick to make the mps full-rank again, which is not guaranteed by ED
943+
ψ₀ = changebonds(first(ψ₀s), SvdCut(; trscheme=notrunc()))
944+
Vspaces = left_virtualspace.(Ref(ψ₀), 1:L)
945+
push!(Vspaces, right_virtualspace(ψ₀, L))
946+
@test all(splat(==), zip(Vspaces, MPSKit.max_virtualspaces(ψ₀)))
947+
948+
# Quasiparticle tests
949+
Es, Bs = excitations(H, QuasiparticleAnsatz(; tol), gs; sector, num=1)
950+
Es = Es .+ E₀
951+
# first excited state is second eigenvalue if sector is trivial
952+
@test Es[1] vals[isone(sector) ? 2 : 1] atol = 1e-8
953+
end
954+
955+
# shifted charges tests
956+
# targeting states with Sz = 1 => vals_shift_dense[0] == vals_dense[1]
957+
# so effectively shifting the charges by -1
958+
H_shift = MPSKit.add_physical_charge(H, U1Irrep.([1, 0, 0, 0]))
959+
H_shift_dense = convert(TensorMap, H_shift)
960+
vals_shift_dense = eigvals(H_shift_dense)
961+
for (sector, vals) in vals_dense
962+
sector′ = only(sector U1Irrep(-1))
963+
@test vals vals_shift_dense[sector′]
964+
end
965+
end
966+
920967
end

test/excis.jl

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using MPSKit, TensorKit
2+
import LinearAlgebra.eigvals
3+
using MPSKit: max_virtualspaces
4+
5+
V = U1Space(i => 1 for i in 0:1)
6+
O = randn(V^2 V^2)
7+
O += O' # Hermitian
8+
9+
N = 3
10+
H = FiniteMPOHamiltonian(fill(V, N), (i, i + 1) => O for i in 1:(N - 1));
11+
12+
h = convert(TensorMap, H);
13+
14+
sectors = collect(blocksectors(h))
15+
sec = U1Irrep(1)
16+
@assert sec in sectors
17+
num = 10
18+
vals1 = eigvals(block(h, sec))
19+
vals2 = eigvals(h)[sec]
20+
@assert vals1 vals2
21+
vals3, vecs = exact_diagonalization(H; sector=(sec), num);
22+
vals1
23+
vals3
24+
25+
right = U1Space(sec => 1)
26+
max_virtualspaces(physicalspace(H); right)
27+
28+
psi_full = rand(oneunit(V) * V^N right);
29+
dim(space(psi_full))
30+
psi = MPSKit.decompose_localmps(psi_full);
31+
left_virtualspace.(psi)
32+
33+
left_virtualspace.(vecs[1].AL)
34+
right_virtualspace.(vecs[1].AL)
35+
max_virtualspaces(physicalspace(H); right)
36+
psi1 = FiniteMPS(physicalspace(H), max_virtualspaces(physicalspace(H))[2:(end - 1)])
37+
psi1, = find_groundstate(psi1, H);
38+
psi2 = FiniteMPS(physicalspace(H), max_virtualspaces(physicalspace(H); right)[2:(end - 1)];
39+
right)
40+
left_virtualspace.(psi2.AL)
41+
right_virtualspace.(psi2.AL)
42+
43+
psi2, = find_groundstate(psi2, H);
44+
expectation_value(psi2, H)
45+
46+
Es, Bs = excitations(H, QuasiparticleAnsatz(), FiniteMPS(psi); sector=sec);
47+
@inferred excitations(H, QuasiparticleAnsatz(), FiniteMPS(psi); sector=sec);
48+
49+
Es .+ expectation_value(psi1, H)
50+
51+
vals1
52+
vals3
53+
54+
psi2
55+
using TestEnv
56+
using Test
57+
TestEnv.activate()
58+
include("setup.jl")
59+
using .TestSetup
60+
using TensorKit, MPSKit
61+
using MPSKit: Multiline
62+
using KrylovKit
63+
64+
H = repeat(TestSetup.sixvertex(), 2)
65+
ψ = InfiniteMPS([ℂ^2, ℂ^2], [ℂ^10, ℂ^10])
66+
ψ, envs, _ = leading_boundary(ψ, H,
67+
VUMPS(; maxiter=400, tol=1e-10))
68+
energies, ϕs = @inferred excitations(H, QuasiparticleAnsatz(),
69+
[0.0, Float64(pi / 2)], ψ,
70+
envs; verbosity=0)
71+
@test abs(energies[1]) > abs(energies[2]) # has a minimum at pi/2
72+
alg = QuasiparticleAnsatz()
73+
ps = [0.0, Float64(pi / 2)]
74+
excitations(H, alg, ps, ψ, envs; verbosity=0);
75+
using Cthulhu
76+
Hm = convert(MultilineMPO, H);
77+
psim = convert(MultilineMPS, ψ);
78+
envs = environments(psim, Hm);
79+
excitations(Hm, alg, ps[1], psim, envs, psim, envs; verbosity=0);
80+
81+
@descend excitations(Hm, alg, ps[1], psim, envs, psim, envs);
82+
83+
qp = Multiline([LeftGaugedQP(rand, psim[1], psim[1]; sector=one(sectortype(psim[1])),
84+
momentum=ps[1])]);
85+
excitations(Hm, alg, qp, envs, envs; num=1);
86+
87+
@descend excitations(Hm, alg, qp, envs, envs; num=1);
88+
@code_warntype excitations(Hm, alg, qp, envs, envs; num=1);
89+
90+
Heff = MPSKit.EffectiveExcitationHamiltonian(H, envs[1], envs[1], fill(1.0, length(H)));
91+
Heffs = Multiline([Heff]);
92+
@code_warntype KrylovKit.apply(Heff, qp[1])
93+
@descend KrylovKit.apply(Heff, qp[1])
94+
95+
KrylovKit.apply(Multiline([Heff]), qp);
96+
@code_warntype KrylovKit.apply(Heffs, qp);
97+
98+
@descend eigsolve(Heffs, qp, 1, :LM, Lanczos());
99+
@code_warntype eigsolve(Heffs, qp, 1, :LM);
100+
@descend eigsolve(Heffs, qp, 1, :LM);

0 commit comments

Comments
 (0)