Skip to content

Commit 750ec89

Browse files
authored
MPO utility and fixes (#256)
* improve `tensorexpr` flexibility * Refactor `convert(TensorMap, ::FiniteMPO(Hamiltonian))` * Bump minimal tensorkit/blocktensorkit versions because `removeunit` * implement `FiniteMPO(Hamiltonian) * TensorMap` * expand operator testset
1 parent a41f935 commit 750ec89

File tree

6 files changed

+133
-99
lines changed

6 files changed

+133
-99
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8"
2424
[compat]
2525
Accessors = "0.1"
2626
Aqua = "0.8.9"
27-
BlockTensorKit = "0.1.1"
27+
BlockTensorKit = "0.1.4"
2828
Compat = "3.47, 4.10"
2929
DocStringExtensions = "0.9.3"
3030
HalfIntegers = "1.6.0"
@@ -38,7 +38,7 @@ Plots = "1.40"
3838
Printf = "1"
3939
Random = "1"
4040
RecipesBase = "1.1"
41-
TensorKit = "0.13, 0.14"
41+
TensorKit = "0.14"
4242
TensorKitManifolds = "0.7"
4343
TensorOperations = "5"
4444
Test = "1"

src/operators/abstractmpo.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,36 @@ function add_physical_charge(O::AbstractBlockTensorMap{<:Any,<:Any,2,2}, charge:
269269
end
270270
return Odst
271271
end
272+
273+
# Contractions
274+
# ------------
275+
# This function usually does not require to be specified for many N, so @generated function is fine?
276+
@generated function _instantiate_finitempo(L::AbstractTensorMap{<:Any,S,1,2},
277+
O::NTuple{N,MPOTensor{S}},
278+
R::AbstractTensorMap{<:Any,S,2,1}) where {N,S}
279+
sites = N + 2
280+
t_out = tensorexpr(:T, -(1:sites), -(1:sites) .- sites)
281+
t_left = tensorexpr(:L, -1, (-1 - sites, 1))
282+
t_mid = ntuple(N) do n
283+
return tensorexpr(:(O[$n]), (n, -n - 1), (-n - sites - 1, n + 1))
284+
end
285+
t_right = tensorexpr(:R, (sites - 1, -sites), -2sites)
286+
ex = :(@plansor $t_out ≔ *($t_left, $t_right, $(t_mid...)))
287+
return macroexpand(@__MODULE__, ex)
288+
end
289+
290+
@generated function _apply_finitempo(x::AbstractTensorMap{<:Any,S,M,A},
291+
L::AbstractTensorMap{<:Any,S,1,2},
292+
O::NTuple{N,MPOTensor{S}},
293+
R::AbstractTensorMap{<:Any,S,2,1}) where {N,M,S,A}
294+
M == N + 2 || throw(ArgumentError("Incompatible number of spaces"))
295+
t_out = tensorexpr(:y, -(1:M), -(1:A) .- M)
296+
t_in = tensorexpr(:x, 1:2:(2M - 1), -(1:A) .- M)
297+
t_left = tensorexpr(:L, -1, (1, 2))
298+
t_mid = ntuple(N) do n
299+
return tensorexpr(:(O[$n]), (2n, -n - 1), (2n + 1, 2n + 2))
300+
end
301+
t_right = tensorexpr(:R, (2N + 2, -M), 2N + 3)
302+
ex = :(@plansor $t_out ≔ *($t_in, $t_left, $t_right, $(t_mid...)))
303+
return macroexpand(@__MODULE__, ex)
304+
end

src/operators/mpo.jl

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,10 @@ function _mps_to_mpo(A::GenericMPSTensor{S,3}) where {S}
9696
end
9797

9898
function Base.convert(::Type{TensorMap}, mpo::FiniteMPO{<:MPOTensor})
99-
N = length(mpo)
100-
# add trivial tensors to remove left and right trivial leg.
101-
V_left = left_virtualspace(mpo, 1)
102-
@assert V_left == oneunit(V_left)
103-
U_left = ones(scalartype(mpo), V_left)'
104-
105-
V_right = right_virtualspace(mpo, length(mpo))
106-
@assert V_right == oneunit(V_right)
107-
U_right = ones(scalartype(mpo), V_right)
108-
109-
tensors = vcat(U_left, parent(mpo), U_right)
110-
indices = [[i, -i, -(2N - i + 1), i + 1] for i in 1:length(mpo)]
111-
pushfirst!(indices, [1])
112-
push!(indices, [N + 1])
113-
O = ncon(tensors, indices)
114-
115-
return repartition(O, N, N)
99+
L = removeunit(mpo[1], 1)
100+
R = removeunit(mpo[end], 4)
101+
M = Tuple(mpo[2:(end - 1)])
102+
return convert(TensorMap, _instantiate_finitempo(L, M, R))
116103
end
117104

118105
# Linear Algebra
@@ -279,6 +266,16 @@ function Base.:*(mpo1::InfiniteMPO, mpo2::InfiniteMPO)
279266
return InfiniteMPO(Os)
280267
end
281268

269+
function Base.:*(mpo::FiniteMPO{<:MPOTensor}, x::AbstractTensorMap)
270+
@assert length(mpo) > 1
271+
@assert numout(x) == length(mpo)
272+
L = removeunit(mpo[1], 1)
273+
M = Tuple(mpo[2:(end - 1)])
274+
R = removeunit(mpo[end], 4)
275+
y = _apply_finitempo(x, L, M, R)
276+
return convert(TensorMap, y)
277+
end
278+
282279
# TODO: I think the fastest order is to start from both ends, and take the overlap at the
283280
# largest virtual space cut, but it might be better to just multithread both sides and meet
284281
# in the middle

src/operators/mpohamiltonian.jl

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -359,23 +359,10 @@ function isemptylevel(H::InfiniteMPOHamiltonian, i::Int)
359359
end
360360

361361
function Base.convert(::Type{TensorMap}, H::FiniteMPOHamiltonian)
362-
N = length(H)
363-
# add trivial tensors to remove left and right trivial leg.
364-
V_left = left_virtualspace(H, 1)
365-
@assert V_left == oneunit(V_left)
366-
U_left = ones(scalartype(H), V_left)'
367-
368-
V_right = right_virtualspace(H, length(H))
369-
@assert V_right == oneunit(V_right)
370-
U_right = ones(scalartype(H), V_right)
371-
372-
tensors = vcat(U_left, parent(H), U_right)
373-
indices = [[i, -i, -(i + N), i + 1] for i in 1:length(H)]
374-
pushfirst!(indices, [1])
375-
push!(indices, [N + 1])
376-
O = convert(TensorMap, ncon(tensors, indices))
377-
378-
return transpose(O, (ntuple(identity, N), ntuple(i -> i + N, N)))
362+
L = removeunit(H[1], 1)
363+
R = removeunit(H[end], 4)
364+
M = Tuple(H[2:(end - 1)])
365+
return convert(TensorMap, _instantiate_finitempo(L, M, R))
379366
end
380367

381368
function add_physical_charge(H::MPOHamiltonian, charges::AbstractVector{<:Sector})
@@ -556,6 +543,15 @@ function Base.:*(H::FiniteMPOHamiltonian, mps::FiniteMPS)
556543
return FiniteMPS(A′)
557544
end
558545

546+
function Base.:*(H::FiniteMPOHamiltonian{<:MPOTensor}, x::AbstractTensorMap)
547+
@assert length(H) > 1
548+
@assert numout(x) == length(H)
549+
L = removeunit(H[1], 1)
550+
M = Tuple(H[2:(end - 1)])
551+
R = removeunit(H[end], 4)
552+
return convert(TensorMap, _apply_finitempo(x, L, M, R))
553+
end
554+
559555
function TensorKit.dot(H₁::FiniteMPOHamiltonian, H₂::FiniteMPOHamiltonian)
560556
check_length(H₁, H₂)
561557

src/utility/utility.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,18 @@ function randomize!(a::AbstractBlockTensorMap)
120120
return a
121121
end
122122

123+
_totuple(t) = t isa Tuple ? t : Tuple(t)
124+
123125
"""
124-
tensorexpr(name::Symbol, ind_out, [ind_in])
126+
tensorexpr(name, ind_out, [ind_in])
125127
126128
Generates expressions for use within [`@tensor`](@extref TensorOperations.@tensor) environments
127129
of the form `name[ind_out...; ind_in]`.
128130
"""
129-
tensorexpr(name::Symbol, inds) = Expr(:ref, name, inds...)
130-
function tensorexpr(name::Symbol, indout, indin)
131-
return Expr(:typed_vcat, name, Expr(:row, indout...), Expr(:row, indin...))
131+
tensorexpr(name, inds) = Expr(:ref, name, _totuple(inds)...)
132+
function tensorexpr(name, indout, indin)
133+
return Expr(:typed_vcat, name, Expr(:row, _totuple(indout)...),
134+
Expr(:row, _totuple(indin)...))
132135
end
133136

134137
function check_length(a, b...)

test/operators.jl

Lines changed: 64 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ vspaces = (ℙ^10, Rep[U₁]((0 => 20)), Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5
4545
mps₁ = FiniteMPS(ψ₁)
4646

4747
@test convert(TensorMap, mpo₁ * mps₁) O₁ * ψ₁
48+
@test mpo₁ * ψ₁ O₁ * ψ₁
4849

4950
@test dot(mps₁, mpo₁, mps₁) dot(ψ₁, O₁, ψ₁)
5051
@test dot(mps₁, mpo₁, mps₁) dot(mps₁, mpo₁ * mps₁)
@@ -60,66 +61,70 @@ end
6061

6162
@testset "Finite MPOHamiltonian" begin
6263
L = 3
63-
lattice = fill(ℂ^2, L)
64-
O₁ = rand(ComplexF64, ℂ^2, ℂ^2)
65-
E = id(storagetype(O₁), domain(O₁))
66-
O₂ = rand(ComplexF64, ℂ^2 *^2, ℂ^2 *^2)
67-
68-
H1 = FiniteMPOHamiltonian(lattice, i => O₁ for i in 1:L)
69-
H2 = FiniteMPOHamiltonian(lattice, (i, i + 1) => O₂ for i in 1:(L - 1))
70-
H3 = FiniteMPOHamiltonian(lattice, 1 => O₁, (2, 3) => O₂, (1, 3) => O₂)
71-
72-
# check if constructor works by converting back to tensormap
73-
H1_tm = convert(TensorMap, H1)
74-
operators = vcat(fill(E, L - 1), O₁)
75-
@test H1_tm mapreduce(+, 1:L) do i
76-
return reduce(, circshift(operators, i))
77-
end
78-
operators = vcat(fill(E, L - 2), O₂)
79-
@test convert(TensorMap, H2) mapreduce(+, 1:(L - 1)) do i
80-
return reduce(, circshift(operators, i))
64+
T = ComplexF64
65+
for V in (ℂ^2, U1Space(0 => 1, 1 => 1))
66+
lattice = fill(V, L)
67+
O₁ = rand(T, V, V)
68+
E = id(storagetype(O₁), domain(O₁))
69+
O₂ = rand(T, V^2 V^2)
70+
71+
H1 = FiniteMPOHamiltonian(lattice, i => O₁ for i in 1:L)
72+
H2 = FiniteMPOHamiltonian(lattice, (i, i + 1) => O₂ for i in 1:(L - 1))
73+
H3 = FiniteMPOHamiltonian(lattice, 1 => O₁, (2, 3) => O₂, (1, 3) => O₂)
74+
75+
# check if constructor works by converting back to tensormap
76+
H1_tm = convert(TensorMap, H1)
77+
operators = vcat(fill(E, L - 1), O₁)
78+
@test H1_tm mapreduce(+, 1:L) do i
79+
return reduce(, circshift(operators, i))
80+
end
81+
operators = vcat(fill(E, L - 2), O₂)
82+
@test convert(TensorMap, H2) mapreduce(+, 1:(L - 1)) do i
83+
return reduce(, circshift(operators, i))
84+
end
85+
@test convert(TensorMap, H3)
86+
O₁ E E + E O₂ + permute(O₂ E, ((1, 3, 2), (4, 6, 5)))
87+
88+
# check if adding terms on the same site works
89+
single_terms = Iterators.flatten(Iterators.repeated((i => O₁ / 2 for i in 1:L), 2))
90+
H4 = FiniteMPOHamiltonian(lattice, single_terms)
91+
@test H4 H1 atol = 1e-6
92+
double_terms = Iterators.flatten(Iterators.repeated(((i, i + 1) => O₂ / 2
93+
for i in 1:(L - 1)), 2))
94+
H5 = FiniteMPOHamiltonian(lattice, double_terms)
95+
@test H5 H2 atol = 1e-6
96+
97+
# test linear algebra
98+
@test H1
99+
FiniteMPOHamiltonian(lattice, 1 => O₁) +
100+
FiniteMPOHamiltonian(lattice, 2 => O₁) +
101+
FiniteMPOHamiltonian(lattice, 3 => O₁)
102+
@test 0.8 * H1 + 0.2 * H1 H1 atol = 1e-6
103+
@test convert(TensorMap, H1 + H2) convert(TensorMap, H1) + convert(TensorMap, H2) atol = 1e-6
104+
105+
# test dot and application
106+
state = rand(T, prod(lattice))
107+
mps = FiniteMPS(state)
108+
109+
@test convert(TensorMap, H1 * mps) H1_tm * state
110+
@test H1 * state H1_tm * state
111+
@test dot(mps, H2, mps) dot(mps, H2 * mps)
112+
113+
# test constructor from dictionary with mixed linear and Cartesian lattice indices as keys
114+
grid = square = fill(V, 3, 3)
115+
116+
local_operators = Dict((I,) => O₁ for I in eachindex(grid))
117+
I_vertical = CartesianIndex(1, 0)
118+
vertical_operators = Dict((I, I + I_vertical) => O₂
119+
for I in eachindex(IndexCartesian(), square)
120+
if I[1] < size(square, 1))
121+
operators = merge(local_operators, vertical_operators)
122+
H4 = FiniteMPOHamiltonian(grid, operators)
123+
124+
@test H4
125+
FiniteMPOHamiltonian(grid, local_operators) +
126+
FiniteMPOHamiltonian(grid, vertical_operators)
81127
end
82-
@test convert(TensorMap, H3)
83-
O₁ E E + E O₂ + permute(O₂ E, ((1, 3, 2), (4, 6, 5)))
84-
85-
# check if adding terms on the same site works
86-
single_terms = Iterators.flatten(Iterators.repeated((i => O₁ / 2 for i in 1:L), 2))
87-
H4 = FiniteMPOHamiltonian(lattice, single_terms)
88-
@test H4 H1 atol = 1e-6
89-
double_terms = Iterators.flatten(Iterators.repeated(((i, i + 1) => O₂ / 2
90-
for i in 1:(L - 1)), 2))
91-
H5 = FiniteMPOHamiltonian(lattice, double_terms)
92-
@test H5 H2 atol = 1e-6
93-
94-
# test linear algebra
95-
@test H1
96-
FiniteMPOHamiltonian(lattice, 1 => O₁) +
97-
FiniteMPOHamiltonian(lattice, 2 => O₁) +
98-
FiniteMPOHamiltonian(lattice, 3 => O₁)
99-
@test 0.8 * H1 + 0.2 * H1 H1 atol = 1e-6
100-
@test convert(TensorMap, H1 + H2) convert(TensorMap, H1) + convert(TensorMap, H2) atol = 1e-6
101-
102-
# test dot and application
103-
state = rand(ComplexF64, prod(lattice))
104-
mps = FiniteMPS(state)
105-
106-
@test convert(TensorMap, H1 * mps) H1_tm * state
107-
@test dot(mps, H2, mps) dot(mps, H2 * mps)
108-
109-
# test constructor from dictionary with mixed linear and Cartesian lattice indices as keys
110-
grid = square = fill(ℂ^2, 3, 3)
111-
112-
local_operators = Dict((I,) => O₁ for I in eachindex(grid))
113-
I_vertical = CartesianIndex(1, 0)
114-
vertical_operators = Dict((I, I + I_vertical) => O₂
115-
for I in eachindex(IndexCartesian(), square)
116-
if I[1] < size(square, 1))
117-
operators = merge(local_operators, vertical_operators)
118-
H4 = FiniteMPOHamiltonian(grid, operators)
119-
120-
@test H4
121-
FiniteMPOHamiltonian(grid, local_operators) +
122-
FiniteMPOHamiltonian(grid, vertical_operators)
123128
end
124129

125130
@testset "InfiniteMPOHamiltonian $(sectortype(pspace))" for (pspace, Dspace) in

0 commit comments

Comments
 (0)