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.
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 # error
ERROR: 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)