Skip to content

Commit 2aa35fd

Browse files
committed
add experimental support for fermions and tensor contractions with super vector spaces
1 parent b537e2b commit 2aa35fd

10 files changed

Lines changed: 168 additions & 41 deletions

File tree

src/TensorKit.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export FusionStyle, UniqueFusion, MultipleFusion, MultiplicityFreeFusion,
1313
SimpleFusion, GenericFusion
1414
export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic
1515
export Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep
16-
#FermionParity, FermionNumber, FermionSpin # specific sectors
16+
export Fermion, FermionParity, FermionNumber, FermionSpin
1717
export FibonacciAnyon
1818
export IsingAnyon
1919

@@ -45,6 +45,7 @@ export Trivial, ZNSpace, SU2Irrep, U1Irrep, CU1Irrep # Fermion
4545
# some unicode
4646
export , , ×, , ℂ, ℝ, ℤ, , , , , , ,
4747
export ℤ₂, ℤ₃, ℤ₄, U₁, SU, SU₂, CU₁
48+
export fℤ₂, fU₁, fSU₂
4849
export ℤ₂Space, ℤ₃Space, ℤ₄Space, U₁Space, CU₁Space, SU₂Space
4950

5051
# tensor maps

src/fusiontrees/manipulations.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ function braid(f::FusionTree{I, N},
875875
for i = 1:N
876876
for j = 1:i-1
877877
if p[j] > p[i]
878-
a, b = f.uncoupled[j], f.uncoupled[i]
878+
a, b = f.uncoupled[p[j]], f.uncoupled[p[i]]
879879
coeff *= Rsymbol(a, b, first(a b))
880880
end
881881
end

src/sectors/fermions.jl

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,92 @@
1-
# TODO
1+
struct Fermion{P,I<:Sector} <: Sector
2+
sector::I
3+
function Fermion{P,I}(sector::I) where {P, I<:Sector}
4+
@assert BraidingStyle(I) isa Bosonic
5+
return new{P,I}(sector)
6+
end
7+
end
8+
Fermion{P}(sector::I) where {P, I<:Sector} = Fermion{P,I}(sector)
9+
Fermion{P,I}(sector) where {P, I<:Sector} = Fermion{P,I}(convert(I, sector))
10+
Base.convert(::Type{Fermion{P,I}}, a::Fermion{P,I}) where {P, I<:Sector} = a
11+
Base.convert(::Type{Fermion{P,I}}, a) where {P, I<:Sector} = Fermion{P,I}(convert(I, a))
12+
13+
fermionparity(f::Fermion{P}) where P = P(f.sector)
14+
15+
Base.IteratorSize(::Type{SectorValues{Fermion{P,I}}}) where {P, I<:Sector} =
16+
Base.IteratorSize(SectorValues{I})
17+
Base.length(::SectorValues{Fermion{P,I}}) where {P, I<:Sector} = length(values(I))
18+
function Base.iterate(::SectorValues{Fermion{P, I}}) where {P, I<:Sector}
19+
next = iterate(values(I))
20+
@assert next !== nothing
21+
value, state = next
22+
return Fermion{P}(value), state
23+
end
24+
function Base.iterate(::SectorValues{Fermion{P, I}}, state) where {P, I<:Sector}
25+
next = iterate(values(I), state)
26+
if next === nothing
27+
return nothing
28+
else
29+
value, state = next
30+
return Fermion{P}(value), state
31+
end
32+
end
33+
Base.getindex(::SectorValues{Fermion{P, I}}, i) where {P, I<:Sector} =
34+
Fermion{P}(values(I)[i])
35+
findindex(::SectorValues{Fermion{P, I}}, f::Fermion{P, I}) where {P, I<:Sector} =
36+
findindex(values(I), f.sector)
37+
38+
Base.one(::Type{Fermion{P, I}}) where {P, I<:Sector} = Fermion{P}(one(I))
39+
Base.conj(f::Fermion{P}) where {P} = Fermion{P}(conj(f.sector))
40+
41+
dim(f::Fermion) = dim(f.sector)
42+
43+
FusionStyle(::Type{<:Fermion{<:Any,I}}) where {I<:Sector} = FusionStyle(I)
44+
BraidingStyle(::Type{<:Fermion}) = Fermionic()
45+
Base.isreal(::Type{Fermion{<:Any,I}}) where {I<:Sector} = isreal(I)
46+
47+
(a::F, b::F) where {F<:Fermion} = SectorSet{F}(a.sector b.sector)
48+
49+
Nsymbol(a::F, b::F, c::F) where {F<:Fermion} = Nsymbol(a.sector, b.sector, c.sector)
50+
51+
Fsymbol(a::F, b::F, c::F, d::F, e::F, f::F) where {F<:Fermion} =
52+
Fsymbol(a.sector, b.sector, c.sector, d.sector, e.sector, f.sector)
53+
54+
function Rsymbol(a::F, b::F, c::F) where {F<:Fermion}
55+
if fermionparity(a) && fermionparity(b)
56+
return -Rsymbol(a.sector, b.sector, c.sector)
57+
else
58+
return +Rsymbol(a.sector, b.sector, c.sector)
59+
end
60+
end
61+
62+
twist(a::Fermion) = ifelse(fermionparity(a), -1, +1)*twist(a.sector)
63+
64+
type_repr(::Type{Fermion{P,I}}) where {P, I<:Sector} = "Fermion{$P, " * type_repr(I) * "}"
65+
66+
function Base.show(io::IO, a::Fermion{P, I}) where {P, I<:Sector}
67+
if get(io, :typeinfo, nothing) !== Fermion{P, I}
68+
print(io, type_repr(typeof(a)), "(")
69+
end
70+
print(IOContext(io, :typeinfo => I), a.sector)
71+
if get(io, :typeinfo, nothing) !== Fermion{P, I}
72+
print(io, ")")
73+
end
74+
end
75+
76+
Base.hash(f::Fermion, h::UInt) = hash(f.sector, h)
77+
Base.isless(a::F, b::F) where {F<:Fermion} = isless(a.sector, b.sector)
78+
79+
_fermionparity(a::Z2Irrep) = isodd(a.n)
80+
_fermionnumber(a::U1Irrep) = isodd(convert(Int, a.charge))
81+
_fermionspin(a::SU2Irrep) = isodd(twice(a.j))
82+
83+
const FermionParity = Fermion{_fermionparity, Z2Irrep}
84+
const FermionNumber = Fermion{_fermionnumber, U1Irrep}
85+
const FermionSpin = Fermion{_fermionspin, SU2Irrep}
86+
const fℤ₂ = FermionParity
87+
const fU₁ = FermionNumber
88+
const fSU₂ = FermionSpin
89+
90+
type_repr(::Type{FermionParity}) = "FermionParity"
91+
type_repr(::Type{FermionNumber}) = "FermionNumber"
92+
type_repr(::Type{FermionSpin}) = "FermionSpin"

src/sectors/irreps.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Nsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = c == first(a ⊗ b)
5757
Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:AbelianIrrep} =
5858
Int(Nsymbol(a, b, e)*Nsymbol(e, c, d)*Nsymbol(b, c, f)*Nsymbol(a, f, d))
5959
frobeniusschur(a::AbelianIrrep) = 1
60+
Asymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))
6061
Bsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))
6162
Rsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))
6263

src/sectors/sectors.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ which case it is complex).
9898
"""
9999
function Base.isreal(I::Type{<:Sector})
100100
u = one(I)
101-
return (eltype(Fsymbol(u, u, u, u, u, u))<:Real) && (eltype(Rsymbol(u, u, u))<:Real)
101+
if BraidingStyle(I) isa HasBraiding
102+
return (eltype(Fsymbol(u, u, u, u, u, u))<:Real) && (eltype(Rsymbol(u, u, u))<:Real)
103+
else
104+
return (eltype(Fsymbol(u, u, u, u, u, u))<:Real)
105+
end
102106
end
103107
Base.isreal(::Type{Trivial}) = true
104108

@@ -379,6 +383,6 @@ end
379383
# possible sectors
380384
include("groups.jl")
381385
include("irreps.jl") # irreps of symmetry groups, with bosonic braiding
382-
# include("fermions.jl") # irreps with defined fermionparity and fermionic braiding
386+
include("fermions.jl") # irreps with defined fermionparity and fermionic braiding
383387
include("anyons.jl") # non-group sectors
384388
include("product.jl") # direct product of different sectors

src/tensors/indexmanipulations.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ codomain or range of the map, and indices in `p2` indicating the domain.
1111
To permute into an existing `tdst`, see [`add!`](@ref)
1212
"""
1313
function permute(t::TensorMap{S},
14-
p1::IndexTuple, p2::IndexTuple=();
15-
copy::Bool = false) where {S}
16-
cod = ProductSpace{S}(map(n->space(t, n), p1))
17-
dom = ProductSpace{S}(map(n->dual(space(t, n)), p2))
18-
14+
p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=();
15+
copy::Bool = false) where {S, N₁, N₂}
16+
cod = ProductSpace{S, N₁}(map(n->space(t, n), p1))
17+
dom = ProductSpace{S, N₂}(map(n->dual(space(t, n)), p2))
18+
# share data if possible
1919
if !copy
20-
# share data if possible
2120
if p1 === codomainind(t) && p2 === domainind(t)
2221
return t
2322
elseif has_shared_permute(t, p1, p2)

src/tensors/tensor.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function TensorMap(data::DenseArray, codom::ProductSpace{S,N₁}, dom::ProductSp
7979
size(data) == (dims(codom)..., dims(dom)...))
8080
throw(DimensionMismatch())
8181
end
82-
if sectortype(S) === Trivial # For now, we can only accept array data for Trivial sectortype
82+
if sectortype(S) === Trivial
8383
data2 = reshape(data, (d1, d2))
8484
A = typeof(data2)
8585
return TensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data2, codom, dom)

src/tensors/tensoroperations.jl

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
function cached_permute(sym::Symbol, t::TensorMap{S},
2-
p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=()) where {S, N₁, N₂}
2+
p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=();
3+
copy::Bool = false) where {S, N₁, N₂}
34
cod = ProductSpace{S, N₁}(map(n->space(t, n), p1))
45
dom = ProductSpace{S, N₂}(map(n->dual(space(t, n)), p2))
5-
66
# share data if possible
7-
if p1 === codomainind(t) && p2 === domainind(t)
8-
return t
9-
elseif isa(t, TensorMap) && sectortype(S) === Trivial
10-
stridet = i->stride(t[], i)
11-
sizet = i->size(t[], i)
12-
canfuse1, d1, s1 = TensorOperations._canfuse(sizet.(p1), stridet.(p1))
13-
canfuse2, d2, s2 = TensorOperations._canfuse(sizet.(p2), stridet.(p2))
14-
if canfuse1 && canfuse2 && s1 == 1 && (d2 == 1 || s2 == d1)
7+
if !copy
8+
if p1 === codomainind(t) && p2 === domainind(t)
9+
return t
10+
elseif has_shared_permute(t, p1, p2)
1511
return TensorMap(reshape(t.data, dim(cod), dim(dom)), cod, dom)
1612
end
1713
end
@@ -23,11 +19,11 @@ function cached_permute(sym::Symbol, t::TensorMap{S},
2319
end
2420

2521
function cached_permute(sym::Symbol, t::AdjointTensorMap{S},
26-
p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=()) where {S, N₁, N₂}
27-
22+
p1::IndexTuple, p2::IndexTuple=();
23+
copy::Bool = false) where {S, N₁, N₂}
2824
p1′ = adjointtensorindices(t, p2)
2925
p2′ = adjointtensorindices(t, p1)
30-
adjoint(cached_permute(sym, adjoint(t), p1′, p2′))
26+
adjoint(cached_permute(sym, adjoint(t), p1′, p2′; copy = copy))
3127
end
3228

3329
scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} =
@@ -254,21 +250,21 @@ function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S},
254250
memcost4 += dB*(!hsp(B, oindB, cindB′′)) +
255251
dA*(!hsp(A, cindA′′, oindA))
256252

257-
if min(memcost1, memcost2) <= min(memcost3, memcost4)
253+
# if min(memcost1, memcost2) <= min(memcost3, memcost4)
258254
if memcost1 <= memcost2
259255
return _contract!(α, A, B, β, C, oindA, cindA′, oindB, cindB′, p1, p2, syms)
260256
else
261257
return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p1, p2, syms)
262258
end
263-
else
264-
p1′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p1)
265-
p2′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p2)
266-
if memcost3 <= memcost4
267-
return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′, syms)
268-
else
269-
return _contract!(α, B, A, β, C, oindB, cindB′′, oindA, cindA′′, p1′, p2′, syms)
270-
end
271-
end
259+
# else
260+
# p1′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p1)
261+
# p2′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p2)
262+
# if memcost3 <= memcost4
263+
# return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′, syms)
264+
# else
265+
# return _contract!(α, B, A, β, C, oindB, cindB′′, oindA, cindA′′, p1′, p2′, syms)
266+
# end
267+
# end
272268
end
273269

274270
function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S},
@@ -278,13 +274,31 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S},
278274
p1::IndexTuple, p2::IndexTuple,
279275
syms::Union{Nothing, NTuple{3, Symbol}} = nothing) where {S, N₁, N₂}
280276

277+
if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding)
278+
throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead"))
279+
end
280+
copyA = false
281+
if BraidingStyle(sectortype(S)) isa Fermionic
282+
for i in cindA
283+
if !isdual(space(A, i))
284+
copyA = true
285+
end
286+
end
287+
end
281288
if syms === nothing
282-
A′ = permute(A, oindA, cindA)
289+
A′ = permute(A, oindA, cindA; copy = copyA)
283290
B′ = permute(B, cindB, oindB)
284291
else
285-
A′ = cached_permute(syms[1], A, oindA, cindA)
292+
A′ = cached_permute(syms[1], A, oindA, cindA; copy = copyA)
286293
B′ = cached_permute(syms[2], B, cindB, oindB)
287294
end
295+
if BraidingStyle(sectortype(S)) isa Fermionic
296+
for i in domainind(A′)
297+
if !isdual(space(A′, i))
298+
A′ = twist!(A′, i)
299+
end
300+
end
301+
end
288302
ipC = TupleTools.invperm((p1..., p2...))
289303
oindAinC = TupleTools.getindices(ipC, ntuple(n->n, N₁))
290304
oindBinC = TupleTools.getindices(ipC, ntuple(n->n+N₁, N₂))

test/runtests.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const TK = TensorKit
1919
Random.seed!(1234)
2020

2121
smallset(::Type{I}) where {I<:Sector} = take(values(I), 5)
22+
smallset(::Type{FermionNumber}) = FermionNumber.((0, +1, -1, +2, -2))
2223
function smallset(::Type{ProductSector{Tuple{I1,I2}}}) where {I1,I2}
2324
iter = product(smallset(I1), smallset(I2))
2425
s = collect(i j for (i,j) in iter if dim(i)*dim(j) <= 6)
@@ -51,9 +52,10 @@ function hasfusiontensor(I::Type{<:Sector})
5152
end
5253

5354
sectorlist = (Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, NewSU2Irrep, SU3Irrep,
54-
FibonacciAnyon, IsingAnyon, Z3Irrep Z4Irrep, U1Irrep SU2Irrep, SU2Irrep
55-
SU2Irrep, NewSU2Irrep NewSU2Irrep, NewSU2Irrep SU2Irrep, SU2Irrep
56-
NewSU2Irrep, Z2Irrep FibonacciAnyon FibonacciAnyon)
55+
FibonacciAnyon, IsingAnyon, FermionParity, FermionNumber, FermionSpin,
56+
FermionParity FermionParity, Z3Irrep Z4Irrep, FermionNumber SU2Irrep,
57+
FermionSpin SU2Irrep, NewSU2Irrep NewSU2Irrep, NewSU2Irrep SU2Irrep,
58+
FermionSpin NewSU2Irrep, Z2Irrep FibonacciAnyon FibonacciAnyon)
5759

5860
Ti = time()
5961
include("sectors.jl")

test/tensors.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ Vℤ₂ = (ℂ[Z2Irrep](0=>1, 1=>1),
88
ℂ[Z2Irrep](0=>3, 1=>2)',
99
ℂ[Z2Irrep](0=>2, 1=>3),
1010
ℂ[Z2Irrep](0=>2, 1=>5))
11+
Vfℤ₂ = (ℂ[FermionParity](0=>1, 1=>1),
12+
ℂ[FermionParity](0=>1, 1=>2)',
13+
ℂ[FermionParity](0=>3, 1=>2)',
14+
ℂ[FermionParity](0=>2, 1=>3),
15+
ℂ[FermionParity](0=>2, 1=>5))
1116
Vℤ₃ = (ℂ[Z3Irrep](0=>1, 1=>2, 2=>2),
1217
ℂ[Z3Irrep](0=>3, 1=>1, 2=>1),
1318
ℂ[Z3Irrep](0=>2, 1=>2, 2=>1)',
@@ -18,6 +23,11 @@ VU₁ = (ℂ[U1Irrep](0=>1, 1=>2, -1=>2),
1823
ℂ[U1Irrep](0=>2, 1=>2, -1=>1)',
1924
ℂ[U1Irrep](0=>1, 1=>2, -1=>3),
2025
ℂ[U1Irrep](0=>1, 1=>3, -1=>3)')
26+
VfU₁ = (ℂ[FermionNumber](0=>1, 1=>2, -1=>2),
27+
ℂ[FermionNumber](0=>3, 1=>1, -1=>1),
28+
ℂ[FermionNumber](0=>2, 1=>2, -1=>1)',
29+
ℂ[FermionNumber](0=>1, 1=>2, -1=>3),
30+
ℂ[FermionNumber](0=>1, 1=>3, -1=>3)')
2131
VCU₁ = (ℂ[CU1Irrep]((0,0)=>1, (0,1)=>2, 1=>1),
2232
ℂ[CU1Irrep]((0,0)=>3, (0,1)=>0, 1=>1),
2333
ℂ[CU1Irrep]((0,0)=>1, (0,1)=>0, 1=>2)',
@@ -28,13 +38,18 @@ VSU₂ = (ℂ[SU2Irrep](0=>3, 1//2=>1),
2838
ℂ[SU2Irrep](1//2=>1, 1=>1)',
2939
ℂ[SU2Irrep](0=>2, 1//2=>2),
3040
ℂ[SU2Irrep](0=>1, 1//2=>1, 3//2=>1)')
41+
VfSU₂ = (ℂ[FermionSpin](0=>3, 1//2=>1),
42+
ℂ[FermionSpin](0=>2, 1=>1),
43+
ℂ[FermionSpin](1//2=>1, 1=>1)',
44+
ℂ[FermionSpin](0=>2, 1//2=>2),
45+
ℂ[FermionSpin](0=>1, 1//2=>1, 3//2=>1)')
3146
VSU₃ = (ℂ[SU3Irrep]((0,0,0)=>3, (1,0,0)=>1),
3247
ℂ[SU3Irrep]((0,0,0)=>3, (2,0,0)=>1)',
3348
ℂ[SU3Irrep]((1,1,0)=>1, (2,1,0)=>1),
3449
ℂ[SU3Irrep]((1,0,0)=>1, (2,0,0)=>1),
3550
ℂ[SU3Irrep]((0,0,0)=>1, (1,0,0)=>1, (1,1,0)=>1)')
3651

37-
for V in (Vtr, Vℤ₂, Vℤ₃, VU₁, VCU₁, VSU₂)#, VSU₃)
52+
for V in (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂)#, VSU₃)
3853
V1, V2, V3, V4, V5 = V
3954
@assert V3 * V4 * V2 V1' * V5' # necessary for leftorth tests
4055
@assert V3 * V4 V1' * V2' * V5' # necessary for rightorth tests

0 commit comments

Comments
 (0)