Skip to content

Commit fe7cac9

Browse files
committed
add ITensor interface
1 parent 4effb91 commit fe7cac9

6 files changed

Lines changed: 381 additions & 0 deletions

File tree

ext/ITensorsExt/ITensorsExt.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module ITensorsExt
2+
3+
using ITensors
4+
using TensorKit
5+
6+
function TensorKit.TensorMap(src::ITensors.ITensor)
7+
if hasqns(src)
8+
return _blocksparse_tensormap(src)
9+
end
10+
return _dense_tensormap(src)
11+
end
12+
13+
14+
include("itensor_utility.jl")
15+
include("ToTensorKit/dense.jl")
16+
include("ToTensorKit/blocksparse.jl")
17+
18+
include("ToITensor/itensor.jl")
19+
20+
end
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
const tk_tensor = Union{TensorMap, BraidingTensor}
2+
3+
function ITensors.ITensor(t::tk_tensor;
4+
tags::AbstractVector{<:AbstractString}=itensor_standard_tags(numind(t)),
5+
ids=itensor_standard_ids(numind(t)),
6+
plevs=itensor_standard_plevs(numind(t)),
7+
qn_names::Union{<:AbstractVector{<:AbstractString},Missing}=missing,
8+
)::ITensor
9+
T = eltype(t)
10+
## First we take t to be a tensor from a 0-dimensional codomain to a rank N-domain
11+
t = repartition(t, numind(t), 0)
12+
sp = TensorKit.space(t)
13+
indices = [[(isdual(V) ? dual(c) : c) => TensorKit.dim(V, c) for c in sectors(V)] for V in sp.codomain]
14+
direction = [isdual(V) ? ITensors.In : ITensors.Out for V in sp.codomain]
15+
direction_sign = [isdual(V) ? -1 : 1 for V in sp.codomain]
16+
if sectortype(sp) <: Trivial
17+
sizes = last.(last.(indices))
18+
index = map(eachindex(indices)) do i
19+
id = ids[i]
20+
dir = direction[i]
21+
tag = tags[i]
22+
plev = plevs[i]
23+
return Index(id, sizes[i], dir, tag, plev)
24+
end
25+
iten = ITensors.DenseTensor(T, undef, index)
26+
copyto!(iten.storage.data, t.data)
27+
return ITensors.itensor(iten)
28+
end
29+
isa_product = sectortype(sp) <: ProductSector
30+
if isa_product
31+
moduli = convert_to_modulus.(fieldtypes(sectortype(sp).parameters[1]))
32+
else
33+
moduli = convert_to_modulus.([sectortype(sp)])
34+
end
35+
36+
ismissing(qn_names) && (qn_names=itensor_standard_qn_names(length(moduli)))
37+
38+
all_u1s = findall(x -> x == 1, moduli)
39+
multiply_by_two=false
40+
for i in eachindex(indices)
41+
space = indices[i]
42+
for (c, _) in space
43+
for i in all_u1s
44+
charge = get_charge(isa_product ? c[i] : c; rescale=true)
45+
if mod(charge,2)!=0
46+
multiply_by_two=true
47+
break
48+
end
49+
end
50+
if multiply_by_two
51+
break
52+
end
53+
end
54+
if multiply_by_two
55+
break
56+
end
57+
end
58+
59+
index_dict=[Dict{Sector, Int}() for _ in eachindex(indices)]
60+
61+
indices = map(eachindex(indices)) do i
62+
space_tensorkit = indices[i]
63+
space = map(x->construct_QN(x[1],direction_sign[i],qn_names,moduli, multiply_by_two, isa_product)=>x[2], space_tensorkit)
64+
index_dict[i] = Dict(zip(first.(space_tensorkit),eachindex(space)))
65+
66+
id = ids[i]
67+
dir = direction[i]
68+
tag = tags[i]
69+
plev = plevs[i]
70+
return Index(id, space, dir, tag, plev)
71+
end
72+
73+
N=numind(t)
74+
75+
all_blocks = ITensors.Block{N}[]
76+
for (f1,f2) in fusiontrees(t)
77+
push!(all_blocks, get_block(f1,f2,index_dict, Val(N)))
78+
end
79+
ITens = ITensors.BlockSparseTensor(T, undef, all_blocks, indices)
80+
for (f1, f2) in fusiontrees(t)
81+
b = get_block(f1,f2,index_dict, Val(N))
82+
ITensors.blockview(ITens, b) .= t[f1, f2]
83+
end
84+
ITens = ITensors.itensor(ITens)
85+
check_flux(ITens)
86+
return combineblocks_itensor(ITens)
87+
end
88+
89+
function construct_QN(x::Sector, sign::Int, qn_names, moduli, multiply_by_two::Bool=false, isa_product::Bool=false)
90+
charges = map(eachindex(moduli)) do i
91+
#charg = sign*
92+
93+
charg = get_charge(isa_product ? x[i] : x; rescale=multiply_by_two)
94+
if !isone(moduli[i])
95+
charg = mod(charg, moduli[i])
96+
end
97+
return charg
98+
end
99+
return QN(map(i->(qn_names[i],charges[i],moduli[i]),eachindex(moduli))...)
100+
end
101+
102+
function get_block(f1::FusionTree, f2::FusionTree, index_dict::Vector{Dict{Sector,Int}},::Val{N}) where {N}
103+
charges= (
104+
(flag ? dual(c) : c for (flag, c) in zip(f1.isdual, f1.uncoupled))...,
105+
(flag ? dual(c) : c for (flag, c) in zip(f2.isdual, f2.uncoupled))...,
106+
)
107+
blockinds=ntuple(i-> index_dict[i][charges[i]], N)
108+
return ITensors.Block{N}(blockinds)
109+
end
110+
111+
function convert_to_modulus(::Type{U1Irrep})
112+
return 1
113+
end
114+
function convert_to_modulus(::Type{ZNIrrep{N}}) where {N}
115+
return N
116+
end
117+
function get_charge(x::ZNIrrep{N}; rescale::Bool=false) where {N}
118+
return Int(x.n)
119+
end
120+
function get_charge(x::U1Irrep; rescale::Bool=false)
121+
if !rescale
122+
return Int(x.charge*1)
123+
else
124+
return Int(x.charge*2)
125+
end
126+
end
127+
128+
function itensor_standard_ids(N::Int)
129+
return [rand(index_id_rng(), ITensors.IDType) for _ in 1:N]
130+
end
131+
function itensor_standard_plevs(N::Int)
132+
return fill(0, N)
133+
end
134+
function itensor_standard_tags(N::Int)
135+
return ["" for _ in 1:N]
136+
end
137+
function itensor_standard_qn_names(N::Int)
138+
return [string(Char('A' + i - 1)) for i in 1:N]
139+
end
140+
function check_flux(T::ITensor)
141+
if !iszero(T)
142+
@assert iszero(flux(T))
143+
end
144+
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
function _blocksparse_tensormap(tsrc::ITensors.ITensor)
2+
N = ndims(tsrc)
3+
T = eltype(tsrc)
4+
indices = map(index, inds(tsrc))
5+
allequal(Base.Fix2(getindex, 2), indices) || error("All QNs in the indices must have consistent symmetry types")
6+
allequal(Base.Fix2(getindex, 3), indices) || error("All QNs in the indices must have consistent QN names")
7+
8+
symmetry = indices[1][2]
9+
10+
charge_and_dims = getindex.(indices, 1)
11+
directions = getindex.(indices, 4)
12+
all_spaces_axes = map((x, y) -> canonicalize_space(x, y; symmetry), charge_and_dims, directions)
13+
all_spaces = first.(all_spaces_axes)
14+
all_axes = last.(all_spaces_axes)
15+
16+
tdst = zeros(T, prod(all_spaces))
17+
t_tensor = ITensors.tensor(tsrc)
18+
for b in eachnzblock(t_tensor)
19+
key = b.data .% Int
20+
block = Array(t_tensor[b])
21+
charges = ntuple(N) do i
22+
c = first(all_axes[i][key[i]])
23+
return isdual(all_spaces[i]) ? dual(c) : c
24+
end
25+
axs = ntuple(N) do i
26+
return last(all_axes[i][key[i]])
27+
end
28+
@assert isapprox(norm(tdst[charges][axs...]), 0; atol = 1.0e-10)
29+
tdst[charges][axs...] .= block
30+
end
31+
32+
return tdst
33+
end
34+
35+
# little bit of annoyance: ITensors allows repeated charges, so have to merge them here
36+
# this helper function merges repeated charges and obtains the total space, while also
37+
# returning the axes for each original charge
38+
function canonicalize_space(inds, arrow; symmetry)
39+
# determine the locations of each entry
40+
axs = map(inds) do (label, dim)
41+
length(label) == 1 && return (convert(symmetry, label[1]) => 1:dim)
42+
return (convert(symmetry, label) => 1:dim)
43+
end
44+
for c in unique(first.(axs))
45+
firstid = true
46+
dlast = 0
47+
for j in findall(==(c), first.(axs))
48+
if firstid
49+
dlast = last(last(axs[j]))
50+
firstid = false
51+
else
52+
axs[j] = (c => (axs[j][2] .+ dlast))
53+
dlast = last(axs[j][2])
54+
end
55+
end
56+
end
57+
58+
# combine everything
59+
combiner = Dict()
60+
for (label, dim) in inds
61+
label′ = length(label) == 1 ? convert(symmetry, label[1]) : convert(symmetry, label)
62+
combiner[label′] = get(combiner, label′, 0) + dim
63+
end
64+
V = Vect[symmetry](label => dim for (label, dim) in combiner; dual = (arrow == -1))
65+
66+
return V, axs
67+
end
68+
69+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function _dense_tensormap(tsrc::ITensors.ITensor)
2+
indices = map(index, inds(tsrc))
3+
4+
dims = getindex.(indices, 1)
5+
directions = getindex.(indices, 4)
6+
all_spaces = map(dims, directions) do dim, arrow
7+
ComplexSpace(dim; dual = (arrow == -1))
8+
end
9+
10+
tdest = zeros(eltype(tsrc), prod(all_spaces))
11+
copyto!(tdest.data, ITensors.tensor(tsrc).storage.data)
12+
return tdest
13+
end

ext/ITensorsExt/itensor_utility.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
function index(ind::Index{T}) where {T<:Integer}
2+
direction = dir(ind) == ITensors.Out || dir(ind) == ITensors.Neither ? 1 : -1
3+
tag = itensor_tag(ind)
4+
return ind.space, direction, plev(ind), tag, ITensors.id(ind)
5+
end
6+
7+
function index(ind::Index{<:AbstractVector{Pair{QN, T}}}) where {T<:Integer}
8+
space = ind.space
9+
blockdims = ITensors.blockdim.(space)
10+
qns = first.(space)
11+
qn_names = [[string(x[i].name) for i in eachindex(x)] for x in qns]
12+
qn_names = filter(x->x!="",unique(reduce(vcat,qn_names)))
13+
len = length(qn_names)
14+
qn_moduli = [[x[i].modulus for i in 1:len] for x in qns]
15+
qn_moduli = [filter(!iszero,unique(getindex.(qn_moduli,i))) for i in 1:len]
16+
qn_moduli = map(x->isempty(x) ? 1 : only(x), qn_moduli)
17+
index_output = [Tuple(x[j].val for j in 1:len)=>blockdims[i] for (i,x) in enumerate(qns)]
18+
19+
symmetry = symmetry_type(Tuple(qn_moduli))
20+
direction = dir(ind)==ITensors.Out ? 1 : -1
21+
22+
tag = itensor_tag(ind)
23+
ids = ITensors.id(ind)
24+
return index_output, symmetry, qn_names, direction, plev(ind), tag, ids
25+
end
26+
27+
function itensor_tag(ind::Index)
28+
ind_tag = tags(ind)
29+
if string(ind_tag[1]) != ""
30+
ind_tag = join(string.(ind_tag), ",")
31+
else
32+
ind_tag = ""
33+
end
34+
return ind_tag
35+
end
36+
37+
function symmetry_type(t::NTuple{N, Int}) where {N}
38+
factors = map(t) do sym
39+
sym == 1 && return U1Irrep
40+
return Irrep[TensorKit.ℤ{sym}]
41+
end
42+
43+
if N == 1
44+
return factors[1]
45+
else
46+
return (factors...)
47+
end
48+
end
49+
50+
function combineblocks_itensor(T::ITensor,inds_old=inds(T))
51+
for i in eachindex(inds_old)
52+
ind_old = inds_old[i]
53+
C = combiner(ind_old; tags=tags(ind_old))
54+
new_ind = uniqueind(C,T)
55+
if inds_old[i].space != new_ind.space
56+
T *= C
57+
new_ind_old_id=Index(ITensors.id(ind_old), new_ind.space, dir(ind_old), tags(ind_old), plev(inds_old[i]))
58+
T *= delta(dag(new_ind),new_ind_old_id)
59+
end
60+
end
61+
return T
62+
end

test/test_itensor.jl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using TensorKitAdapters
2+
using Test
3+
using TestExtras
4+
using ITensors
5+
using TensorKit
6+
7+
@testset "ITensor" begin
8+
for T in [ComplexF64,ComplexF64, Float64]
9+
for tag in [missing, "Link","Site,c=2,n=1"]
10+
for plev in Iterators.product(0:2,0:4)
11+
plev[1] == plev[2] && continue
12+
13+
tags = ismissing(tag) ? missing : [tag, tag]
14+
if ismissing(tag)
15+
i = Index(10;dir=ITensors.Out)
16+
iqn = ITensors.combineblocks(Index([QN(("N",2))=>5, QN("N",0)=>5]))[1]
17+
iqn2 = ITensors.combineblocks(Index([QN(("N",2))=>3, QN(("N",0),("P",3))=>7]))[1]
18+
## This guarantees consistent sorting of the blocks for the comparison
19+
## Here, we test combining blocks with the same QN:
20+
iqn3 = Index([QN("Sz",-1)=>2, QN("Sz",1)=>14,QN("Sz",-1)=>4])
21+
else
22+
i = Index(10;dir=ITensors.Out,tags=tag)
23+
iqn = ITensors.combineblocks(Index([QN(("N",2))=>5, QN("N",0)=>5];tags=tag))[1]
24+
iqn2 = ITensors.combineblocks(Index([QN(("N",2))=>3, QN(("N",0),("P",3))=>7];tags=tag))[1]
25+
iqn3 = Index([QN("Sz",-1)=>2, QN("Sz",1)=>14,QN("Sz",-1)=>4]; tags=tag)
26+
end
27+
28+
i_p1 = prime(i, plev[1])
29+
iqn_p1 = prime(iqn, plev[1])
30+
iqn2_p1 = prime(iqn2, plev[1])
31+
iqn3_p1 = prime(iqn3, plev[1])
32+
33+
i_p2 = dag(prime(i, plev[2]))
34+
iqn_p2 = dag(prime(iqn, plev[2]))
35+
iqn2_p2 = dag(prime(iqn2, plev[2]))
36+
iqn3_p2 = dag(prime(iqn3, plev[2]))
37+
38+
A = random_itensor(T,i_p1, i_p2)
39+
B = random_itensor(T,iqn_p1, iqn_p2)
40+
C = random_itensor(T,iqn2_p1, iqn2_p2)
41+
D = random_itensor(T,iqn3_p1, iqn3_p2)
42+
43+
x = TensorMap(A)
44+
y = TensorMap(B)
45+
z = TensorMap(C)
46+
w = TensorMap(D)
47+
48+
if ismissing(tag)
49+
A2 = ITensor(x;ids=ITensors.id.(inds(A)),plevs=plev,qn_names=["N","P"])
50+
B2 = ITensor(y;ids=ITensors.id.(inds(B)),plevs=plev,qn_names=["N"])
51+
C2 = ITensor(z;ids=ITensors.id.(inds(C)),plevs=plev,qn_names=["N","P"])
52+
else
53+
A2 = ITensor(x;ids=ITensors.id.(inds(A)),tags=tags,plevs=plev,qn_names=["N","P"])
54+
B2 = ITensor(y;ids=ITensors.id.(inds(B)),tags=tags,plevs=plev,qn_names=["N"])
55+
C2 = ITensor(z;ids=ITensors.id.(inds(C)),tags=tags,plevs=plev,qn_names=["N","P"])
56+
end
57+
@test isapprox(norm(A2-A),0;atol=1.0e-10)
58+
@test isapprox(norm(B2-B),0;atol=1.0e-10)
59+
@test isapprox(norm(C2-C),0;atol=1.0e-10)
60+
61+
62+
x2 = TensorMap(A2)
63+
y2 = TensorMap(B2)
64+
z2 = TensorMap(C2)
65+
w2 = TensorMap(ITensor(w;ids=ITensors.id.(inds(D)),plevs=plev,qn_names=["N","P"]))
66+
@test isapprox(norm(x2 - x), 0; atol=1.0e-10)
67+
@test isapprox(norm(y2 - y), 0; atol=1.0e-10)
68+
@test isapprox(norm(z2 - z), 0; atol=1.0e-10)
69+
@test isapprox(norm(w2 - w), 0; atol=1.0e-10)
70+
end
71+
end
72+
end
73+
end

0 commit comments

Comments
 (0)