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 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.

Note

The operator is used in both TensorKit and BlockTensorKit, and therefore it must be explicitly imported to avoid name clashes. Both functions achieve almost the same thing, as BlockTensorKit.⊕ can be thought of as a lazy version of TensorKit.⊕.

julia> using TensorKit, BlockTensorKit
julia> using BlockTensorKit: ⊕
julia> 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)6
julia> isdual(V)false
julia> isdual(V')true
julia> field(V)
julia> spacetype(V)SumSpace{TensorKit.ComplexSpace}
julia> 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)3
julia> 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ℂ^2
julia> V1 ⊗ V2 ⊗ V1' == V1 * V2 * V1' == ProductSpace(V1,V2,V1') == ProductSpace(V1,V2) ⊗ V1'true
julia> V1^3((ℂ^1 ⊕ ℂ^2 ⊕ ℂ^3) ⊗ (ℂ^1 ⊕ ℂ^2 ⊕ ℂ^3) ⊗ (ℂ^1 ⊕ ℂ^2 ⊕ ℂ^3))
julia> dim(V1 ⊗ V2)12
julia> dims(V1 ⊗ V2)(6, 2)
julia> dual(V1 ⊗ V2)(⊕((ℂ^2)') ⊗ ((ℂ^1)' ⊕ (ℂ^2)' ⊕ (ℂ^3)'))
julia> spacetype(V1 ⊗ V2)TensorKit.ComplexSpace
julia> 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.ComplexSpace
julia> spacetype(typeof(W))TensorKit.ComplexSpace
julia> 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}: [:, :, 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)