Direct Sum Spaces
The underlying concept that defines any array (or operator) that has some blocked structure is that of a direct sum of vector spaces. These spaces are a natural extension of the TensorKit vector spaces, and you can think of them as a way to lazily concatenate multiple vector spaces into one.
SumSpace
In BlockTensorKit, we provide a type SumSpace that allows you to define such direct sums. They can be defined either directly via the constructor, or by using the ⊞ (\boxplus<TAB>) operator. In order for the direct sum to be wll-defined, all components must have the same value of isdual.
Essentially, that is all there is to it, and you can now use these SumSpace objects much in the same way as you would use an IndexSpace object in TensorKit. In particular, it adheres to the interface of ElementarySpace, which means that you can query the properties as you would expect.
The notion of a direct sum of vector spaces is used in both TensorKit (⊕ or oplus) and BlockTensorKit (⊞ or boxplus). Both functions achieve almost the same thing, and BlockTensorKit.⊞ can be thought of as a lazy version of TensorKit.⊕.
julia> using TensorKit, BlockTensorKitjulia> V = ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3(ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3)julia> ℂ^2 ⊞ (ℂ^2)' ⊞ ℂ^2 # errorERROR: SpaceMismatch("Direct sum of a vector space and its dual does not exist")julia> dim(V)6julia> isdual(V)falsejulia> isdual(V')truejulia> field(V)ℂjulia> spacetype(V)TensorKit.ComplexSpacejulia> InnerProductStyle(V)TensorKit.EuclideanInnerProduct()
The main difference is that the object retains the information about the individual spaces, and you can query them by indexing into the object.
julia> length(V)3julia> V[1]ℂ^1
ProductSumSpace and TensorMapSumSpace
Because these objects are naturally ElementarySpace objects, they can be used in the construction of ProductSpace and HomSpace objects, and in particular, they can be used to define the spaces of TensorMap objects. Additionally, when mixing spaces and their sumspaces, all components are promoted to SumSpace instances.
julia> V1 = ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3(ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3)julia> V2 = ℂ^2ℂ^2julia> V1 ⊗ V2 ⊗ V1' == V1 * V2 * V1' == ProductSpace(V1,V2,V1') == ProductSpace(V1,V2) ⊗ V1'truejulia> V1^3((ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3) ⊗ (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3) ⊗ (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3))julia> dim(V1 ⊗ V2)12julia> dims(V1 ⊗ V2)(6, 2)julia> dual(V1 ⊗ V2)(⊞((ℂ^2)') ⊗ ((ℂ^1)' ⊞ (ℂ^2)' ⊞ (ℂ^3)'))julia> spacetype(V1 ⊗ V2)TensorKit.ComplexSpacejulia> spacetype(typeof(V1 ⊗ V2))TensorKit.ComplexSpace
julia> W = V1 → V2⊞(ℂ^2) ← (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3)julia> field(W)ℂjulia> dual(W)((ℂ^1)' ⊞ (ℂ^2)' ⊞ (ℂ^3)') ← ⊞((ℂ^2)')julia> adjoint(W)(ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3) ← ⊞(ℂ^2)julia> spacetype(W)TensorKit.ComplexSpacejulia> spacetype(typeof(W))TensorKit.ComplexSpacejulia> W[1]⊞(ℂ^2)julia> W[2]((ℂ^1)' ⊞ (ℂ^2)' ⊞ (ℂ^3)')julia> dim(W)12
SumSpaceIndices
Finally, since the SumSpace object is the underlying structure of a blocked tensor, it can be convenient to have a way to obtain the vector spaces of the constituent parts. For this, we provide the SumSpaceIndices object, which can be used to efficiently iterate over the indices of the individual spaces. In particular, we expose the eachspace function, similar to eachindex, to obtain such an iterator.
julia> W = V1 * V2 → V2 * V1(⊞(ℂ^2) ⊗ (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3)) ← ((ℂ^1 ⊞ ℂ^2 ⊞ ℂ^3) ⊗ ⊞(ℂ^2))julia> eachspace(W)1×3×3×1 SumSpaceIndices{TensorKit.ComplexSpace, 2, 2, 4}: [:, :, 1, 1] = (ℂ^2 ⊗ ℂ^1) ← (ℂ^1 ⊗ ℂ^2) … (ℂ^2 ⊗ ℂ^3) ← (ℂ^1 ⊗ ℂ^2) [:, :, 2, 1] = (ℂ^2 ⊗ ℂ^1) ← (ℂ^2 ⊗ ℂ^2) … (ℂ^2 ⊗ ℂ^3) ← (ℂ^2 ⊗ ℂ^2) [:, :, 3, 1] = (ℂ^2 ⊗ ℂ^1) ← (ℂ^3 ⊗ ℂ^2) … (ℂ^2 ⊗ ℂ^3) ← (ℂ^3 ⊗ ℂ^2)