1- using BlockArrays: AbstractBlockedUnitRange
1+ using BlockArrays: AbstractBlockedUnitRange, blocklengths
22
33# Represents the range `1:1` or `Base.OneTo(1)`.
44struct OneToOne{T} <: AbstractUnitRange{T} end
55OneToOne () = OneToOne {Bool} ()
66Base. first (a:: OneToOne ) = one (eltype (a))
77Base. last (a:: OneToOne ) = one (eltype (a))
8+ BlockArrays. blockaxes (g:: OneToOne ) = (Block .(g),) # BlockArrays default crashes for OneToOne{Bool}
89
910# https://github.com/ITensor/ITensors.jl/blob/v0.3.57/NDTensors/src/lib/GradedAxes/src/tensor_product.jl
1011# https://en.wikipedia.org/wiki/Tensor_product
@@ -18,23 +19,25 @@ function tensor_product(
1819 return foldl (tensor_product, (a1, a2, a3, a_rest... ))
1920end
2021
22+ flip_dual (r:: AbstractUnitRange ) = r
23+ flip_dual (r:: UnitRangeDual ) = flip (r)
2124function tensor_product (a1:: AbstractUnitRange , a2:: AbstractUnitRange )
22- return error ( " Not implemented yet. " )
25+ return tensor_product ( flip_dual (a1), flip_dual (a2) )
2326end
2427
2528function tensor_product (a1:: Base.OneTo , a2:: Base.OneTo )
2629 return Base. OneTo (length (a1) * length (a2))
2730end
2831
29- function tensor_product (a1 :: OneToOne , a2:: AbstractUnitRange )
32+ function tensor_product (:: OneToOne , a2:: AbstractUnitRange )
3033 return a2
3134end
3235
33- function tensor_product (a1:: AbstractUnitRange , a2 :: OneToOne )
36+ function tensor_product (a1:: AbstractUnitRange , :: OneToOne )
3437 return a1
3538end
3639
37- function tensor_product (a1 :: OneToOne , a2 :: OneToOne )
40+ function tensor_product (:: OneToOne , :: OneToOne )
3841 return OneToOne ()
3942end
4043
@@ -45,27 +48,28 @@ function fuse_labels(x, y)
4548end
4649
4750function fuse_blocklengths (x:: Integer , y:: Integer )
48- return x * y
51+ # return blocked unit range to keep non-abelian interface
52+ return blockedrange ([x * y])
4953end
5054
5155using .. LabelledNumbers: LabelledInteger, label, labelled, unlabel
5256function fuse_blocklengths (x:: LabelledInteger , y:: LabelledInteger )
53- return labelled (unlabel (x) * unlabel (y), fuse_labels (label (x), label (y)))
57+ # return blocked unit range to keep non-abelian interface
58+ return blockedrange ([labelled (x * y, fuse_labels (label (x), label (y)))])
5459end
5560
5661using BlockArrays: blockedrange, blocks
5762function tensor_product (a1:: AbstractBlockedUnitRange , a2:: AbstractBlockedUnitRange )
58- blocklengths = map (vec ( collect (Iterators. product (blocks (a1), blocks (a2))))) do x
59- return mapreduce (length, fuse_blocklengths, x )
63+ nested = map (Iterators . flatten ( (Iterators. product (blocks (a1), blocks (a2)), ))) do it
64+ return mapreduce (length, fuse_blocklengths, it )
6065 end
61- return blockedrange (blocklengths)
66+ new_blocklengths = mapreduce (blocklengths, vcat, nested)
67+ return blockedrange (new_blocklengths)
6268end
6369
64- function blocksortperm (a:: AbstractBlockedUnitRange )
65- # TODO : Figure out how to deal with dual sectors.
66- # TODO : `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`.
67- # # return Block.(sortperm(nondual_sectors(a); rev=isdual(a)))
68- return Block .(sortperm (blocklabels (a)))
70+ # convention: sort UnitRangeDual according to nondual blocks
71+ function blocksortperm (a:: AbstractUnitRange )
72+ return Block .(sortperm (blocklabels (nondual (a))))
6973end
7074
7175using BlockArrays: Block, BlockVector
8286# Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`.
8387# Get the permutation for sorting, then group by common elements.
8488# groupsortperm([2, 1, 2, 3]) == [[2], [1, 3], [4]]
85- function blockmergesortperm (a:: AbstractBlockedUnitRange )
86- # If it is dual, reverse the sorting so the sectors
87- # end up sorted in the same way whether or not the space
88- # is dual.
89- # TODO : Figure out how to deal with dual sectors.
90- # TODO : `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`.
91- # # return Block.(groupsortperm(nondual_sectors(a); rev=isdual(a)))
92- return Block .(groupsortperm (blocklabels (a)))
89+ function blockmergesortperm (a:: AbstractUnitRange )
90+ return Block .(groupsortperm (blocklabels (nondual (a))))
9391end
9492
9593# Used by `TensorAlgebra.splitdims` in `BlockSparseArraysGradedAxesExt`.
9694invblockperm (a:: Vector{<:Block{1}} ) = Block .(invperm (Int .(a)))
9795
98- # Used by `TensorAlgebra.fusedims` in `BlockSparseArraysGradedAxesExt`.
99- function blockmergesortperm (a:: GradedUnitRange )
100- # If it is dual, reverse the sorting so the sectors
101- # end up sorted in the same way whether or not the space
102- # is dual.
103- # TODO : Figure out how to deal with dual sectors.
104- # TODO : `rev=isdual(a)` may not be correct for symmetries beyond `U(1)`.
105- return Block .(groupsortperm (blocklabels (a)))
96+ function blockmergesort (g:: AbstractGradedUnitRange )
97+ glabels = blocklabels (g)
98+ gblocklengths = blocklengths (g)
99+ new_blocklengths = map (sort (unique (glabels))) do la
100+ return labelled (sum (gblocklengths[findall (== (la), glabels)]; init= 0 ), la)
101+ end
102+ return gradedrange (new_blocklengths)
103+ end
104+
105+ blockmergesort (g:: UnitRangeDual ) = flip (blockmergesort (flip (g)))
106+ blockmergesort (g:: AbstractUnitRange ) = g
107+
108+ # fusion_product produces a sorted, non-dual GradedUnitRange
109+ function fusion_product (g1, g2)
110+ return blockmergesort (tensor_product (g1, g2))
111+ end
112+
113+ fusion_product (g:: AbstractUnitRange ) = blockmergesort (g)
114+ fusion_product (g:: UnitRangeDual ) = fusion_product (flip (g))
115+
116+ # recursive fusion_product. Simpler than reduce + fix type stability issues with reduce
117+ function fusion_product (g1, g2, g3... )
118+ return fusion_product (fusion_product (g1, g2), g3... )
106119end
0 commit comments