Skip to content

Commit 61d5bbb

Browse files
authored
Generalize infsiteinds to fractional QN densities (#69)
1 parent 167abfb commit 61d5bbb

File tree

5 files changed

+158
-9
lines changed

5 files changed

+158
-9
lines changed

src/infinitecanonicalmps.jl

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,49 @@ end
55

66
# TODO: Move to ITensors.jl
77
function Base.:/(qnval::ITensors.QNVal, n::Int)
8-
div_val = ITensors.val(qnval) / n
9-
if !isinteger(div_val)
10-
error("Dividing $qnval by $n, the resulting QN value is not an integer")
8+
if abs(qnval.modulus) <= 1
9+
div_val = ITensors.val(qnval) / n
10+
if !isinteger(div_val)
11+
error("Dividing $qnval by $n, the resulting QN value is not an integer")
12+
end
13+
return setval(qnval, Int(div_val))
14+
else
15+
if mod(qnval.val, gcd(qnval.modulus, n)) != 0
16+
error("Dividing $qnval by $n, no solution to the Chinese remainder theorem")
17+
end
18+
#We look for the inverse of n in the equation n x = qnval.val mod qn.modulus.
19+
#The Chinese remainder theorem guarantees it exists
20+
#We perform a brute force sieving solution -> should be ok for all reasonnable n
21+
sol = qnval.val
22+
for x in 1:n
23+
if mod(sol, n) != 0
24+
sol += qnval.modulus
25+
else
26+
break
27+
end
28+
x == n && error("Failed to find solution") #Bezut identity would be a better solver here
29+
end
30+
modulus = lcm(qnval.modulus, n)
31+
sol = mod(sol, modulus)
32+
sol = sol < abs(sol - modulus) ? sol : sol - modulus
33+
return setval(qnval, Int(sol / n))
1134
end
12-
return setval(qnval, Int(div_val))
35+
end
36+
37+
function Base.:*(qnval::ITensors.QNVal, n::Int)
38+
prod_val = ITensors.val(qnval) * n
39+
return setval(qnval, Int(prod_val))
1340
end
1441

1542
# TODO: Move to ITensors.jl
1643
function Base.:/(qn::QN, n::Int)
1744
return QN(map(qnval -> qnval / n, qn.data))
1845
end
1946

47+
function Base.:*(qn::QN, n::Int)
48+
return QN(map(qnval -> qnval * n, qn.data))
49+
end
50+
2051
# of Index (Tuple, Vector, ITensor, etc.)
2152
indtype(i::Index) = typeof(i)
2253
indtype(T::Type{<:Index}) = T
@@ -48,11 +79,81 @@ function shift_flux(i::Index, flux_density::QN)
4879
return ITensors.setspace(i, shift_flux(space(i), flux_density))
4980
end
5081

82+
function scale_flux(qn::ITensors.QNVal, flux_factor::Int)
83+
flux_factor == 1 && return qn #This test could occur earlier
84+
if -1 <= qn.modulus <= 1 #U(1) symmetry, should remain a U(1) symmetry, if qn.modulus = 0, it is a dummy term and the code does nothing
85+
return qn * flux_factor
86+
else #To get the same algebra, multiplying the modulus by flux_factor works
87+
return ITensors.QNVal(qn.name, qn.val * flux_factor, qn.modulus * flux_factor)
88+
end
89+
end
90+
91+
function scale_flux(qn::ITensors.QNVal, flux_factor::Dict{ITensors.SmallString,Int})
92+
!haskey(flux_factor, qn.name) && return qn #Ensure that we do not touch empty QNs or QNs that we do not need to rescale because the shift is 0
93+
return scale_flux(qn, flux_factor[qn.name])
94+
end
95+
96+
function scale_flux(qn::QN, flux_factor::Union{Int,Dict{ITensors.SmallString,Int}})
97+
return QN(map(qnval -> scale_flux(qnval, flux_factor), qn.data))
98+
end
99+
100+
function scale_flux(
101+
qnblock::Pair{QN,Int}, flux_factor::Union{Int,Dict{ITensors.SmallString,Int}}
102+
)
103+
return (scale_flux(ITensors.qn(qnblock), flux_factor) => ITensors.blockdim(qnblock))
104+
end
105+
106+
function scale_flux(
107+
space::Vector{Pair{QN,Int}}, flux_factor::Union{Int,Dict{ITensors.SmallString,Int}}
108+
)
109+
return map(qnblock -> scale_flux(qnblock, flux_factor), space)
110+
end
111+
112+
function scale_flux(i::Index, flux_factor::Union{Int,Dict{ITensors.SmallString,Int}})
113+
return ITensors.setspace(i, scale_flux(space(i), flux_factor))
114+
end
115+
51116
function shift_flux_to_zero(s::Vector{<:Index}, flux::QN)
52117
if iszero(flux)
53118
return s
54119
end
120+
#We are introducing a factor per QN
55121
n = length(s)
122+
multipliers = Dict{ITensors.SmallString,Int}() #multipliers is assigned using the names
123+
for qn in flux.data
124+
if qn.val == 0 #We do not need to multiply in this case. It also takes into account the empty QNs which have val and modulus 0
125+
continue
126+
end
127+
multipliers[qn.name] = abs(lcm(qn.val, n) ÷ qn.val)#default solution with positive multiplier
128+
if abs(qn.modulus) > 1
129+
#=
130+
for Zp symmetries, we use the Chinese remainder theorem to find an optimal solution.
131+
We try to find y such that y = 0 mod n and y = qn.val mod qn.modulus. Then we just need to find x such that y = n x.
132+
133+
This system admits a solution iff qn.val = 0 mod( gcd(qn.modulus, n)), which is not necessarily true.
134+
In order to avoid this problem, we introduce a global multiplier b: qn.val -> b qn.val, qn.modulus -> b qn.modulus.
135+
If b = n, we always have n qn.val = 0 mod( gcd(n qn.modulus, n) = n )
136+
We can do better: I try to find the smallest b such that:
137+
b qn.val = 0 mod gcd(n, qn.modulus b).
138+
139+
Case when things changed: n = 3, initstate = +--, flux = 1 mod 2.
140+
Previous version: 1 is not divisible by 3, so we mutiply everything by 3. Gives QN ( 4 mod 6, 1 mod 6)
141+
Current version: gcd(2, 3) = 1, so a solution actually exists: 3 x 1 = 1 mod 2 => work with QN (0 mod 2, 1 mod 2)
142+
=#
143+
multipliers[qn.name] = abs(lcm(qn.val, n) ÷ qn.val)
144+
for b in 1:(multipliers[qn.name] - 1)
145+
if mod(n, b) != 0 #It is easy to show that the optimal b divides n
146+
continue
147+
end
148+
if mod(qn.val * b, gcd(n, b * qn.modulus)) == 0
149+
multipliers[qn.name] = b
150+
break
151+
end
152+
end
153+
end
154+
end
155+
s = map(sₙ -> scale_flux(sₙ, multipliers), s)
156+
flux = scale_flux(flux, multipliers)
56157
flux_density = flux / n
57158
return map(sₙ -> shift_flux(sₙ, flux_density), s)
58159
end

src/subspace_expansion.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function generate_twobody_nullspace(
1818

1919
range_H = nrange(ψ, H[1])
2020

21-
@assert range_H > 1 "Not defined for purely local Hamiltonians"
21+
@assert range_H > 1 "Subspace expansion for InfiniteSum{MPO} is not defined for purely local Hamiltonians"
2222

2323
if range_H > 2
2424
ψᴴ = dag(ψ)
@@ -181,7 +181,7 @@ function subspace_expansion(
181181
@assert==
182182
if maxdim
183183
println(
184-
"Current bond dimension at bond $b is $dˡ while desired maximum dimension is $maxdim, skipping bond dimension increase",
184+
"Current bond dimension at bond $b is $dˡ while desired maximum dimension is $maxdim, skipping bond dimension increase at $b",
185185
)
186186
flush(stdout)
187187
flush(stderr)
@@ -199,7 +199,7 @@ function subspace_expansion(
199199
#Added due to crash during testing
200200
if norm(ψHN2.tensor) < 1e-12
201201
println(
202-
"Impossible to do a subspace expansion, probably due to conservation constraints"
202+
"The two-site subspace expansion produced a zero-norm expansion at $b. This is likely due to the long-range nature of the QN conserving Hamiltonian.",
203203
)
204204
flush(stdout)
205205
flush(stderr)

test/test_unitcell_qns.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using ITensors
2+
using ITensorInfiniteMPS
3+
using Test
4+
using Random
5+
6+
@testset "unitcell_qns" begin
7+
function initstate(n)
8+
if n % 2 == 0
9+
if (n / 2) % 2 == 1
10+
return "Dn"
11+
else
12+
return "Up"
13+
end
14+
else
15+
if (n + 1) / 2 <= 19
16+
if ((n + 1) / 2) % 2 == 1
17+
return "Up"
18+
else
19+
return "Dn"
20+
end
21+
else
22+
return "Emp"
23+
end
24+
end
25+
end
26+
conserve_qns = true
27+
N = 62
28+
s = infsiteinds(n -> isodd(n) ? "tJ" : "S=1/2", N; conserve_qns, initstate)
29+
ψ = InfMPS(s, initstate)
30+
@show N
31+
@show s
32+
33+
function initstate(n)
34+
if mod(n, 4) == 1
35+
return 2
36+
else
37+
return 1
38+
end
39+
end
40+
41+
for N in 1:6
42+
s = infsiteinds("S=1/2", N; conserve_szparity=true, initstate)
43+
@show N
44+
@show s
45+
ψ = InfMPS(s, initstate)
46+
@test iszero(flux.AL))
47+
end
48+
end

test/test_vumpsmpo.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function expect_three_site(ψ::MPS, h::ITensor, n::Int)
99
end
1010

1111
#Ref time is 21.6s with negligible compilation time
12-
@time @testset "vumpsmpo_ising" begin
12+
@testset "vumpsmpo_ising" begin
1313
Random.seed!(1234)
1414

1515
model = Model("ising")

test/test_vumpsmpo_fqhe.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ end
2121

2222
#Currently, VUMPS cannot give the right result as the subspace expansion is too small
2323
#This is meant to test the generalized translation rules
24-
@time @testset "vumpsmpo_fqhe" begin
24+
@testset "vumpsmpo_fqhe" begin
2525
Random.seed!(1234)
2626
function initstate(n)
2727
if mod(n, 3) == 1

0 commit comments

Comments
 (0)