Index manipulations
A TensorMap{T, S, N₁, N₂} is a linear map from a domain (a ProductSpace{S, N₂}) to a codomain (a ProductSpace{S, N₁}). In practice, the bipartition of the N₁ + N₂ indices between domain and codomain rarely remains fixed: algorithms typically need to reshuffle indices between the two sides, reorder them, or change the arrow direction on individual indices before passing a tensor to a factorization or contraction.
Index manipulations cover all such operations. They act on the structure of the tensor data in a way that is fully determined by the categorical data of the sectortype, such that TensorKit automatically manipulates the tensor entries accordingly. The operations fall into three groups, which mirror the structure of the source file:
- Reweighting:
flipandtwistapply local isomorphisms to individual indices without changing the index structure. - Space insertion/removal:
insertleftunit,insertrightunitandremoveunitadd or remove trivial (scalar) index factors. - Index rearrangements:
permute,braid,transposeandrepartitionreorder indices and/or move them between domain and codomain.
Throughout this page, new index positions are specified using Index2Tuple{N₁, N₂}, i.e. a pair (p₁, p₂) of index tuples. The indices listed in p₁ form the new codomain and those in p₂ form the new domain. The following helpers retrieve the current index structure of a tensor:
TensorKit.numout — Function
numout(x) -> Int
numout(T::Type) -> IntReturn the length of the codomain, i.e. the number of output spaces. By default, this is implemented in the type domain.
TensorKit.numin — Function
numin(x) -> Int
numin(T::Type) -> IntReturn the length of the domain, i.e. the number of input spaces. By default, this is implemented in the type domain.
TensorKit.numind — Function
numind(x) -> Int
numind(T::Type) -> Int
order(x) = numind(x)Return the total number of input and output spaces, i.e. numin(x) + numout(x). Alternatively, the alias order can also be used.
TensorKit.codomainind — Function
TensorKit.domainind — Function
TensorKit.allind — Function
allind(x) -> Tuple{Int}Return all indices, i.e. the indices of both domain and codomain.
See also codomainind and domainind.
Reweighting
Reweighting operations modify the entries of a tensor by applying local isomorphisms to individual indices, without changing the number of indices or their partition between domain and codomain. In particular, twist applies the topological spin (monoidal twist) to selected indices; this operation preserves the space of the indices and is completely trivial for BraidingStyle(I) == Bosonic(). In contrast, flip changes the arrow direction on selected indices by applying a (non-canonical!) isomorphism between the index space and its dual.
TensorKitSectors.twist — Method
twist(tsrc::AbstractTensorMap, i::Int; inv::Bool = false, copy::Bool = false) -> tdst
twist(tsrc::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false) -> tdstApply a twist to the ith index of tsrc and return the result as a new tensor. If inv = true, use the inverse twist. If copy = false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.
See twist! for storing the result in place.
TensorKit.twist! — Function
twist!(t::AbstractTensorMap, i::Int; inv::Bool = false) -> t
twist!(t::AbstractTensorMap, inds; inv::Bool = false) -> tApply a twist to the ith index of t, or all indices in inds, storing the result in t. If inv=true, use the inverse twist.
See twist for creating a new tensor.
TensorKit.flip — Method
flip(t::AbstractTensorMap, I) -> t′::AbstractTensorMapReturn a new tensor that is isomorphic to t but where the arrows on the indices i that satisfy i ∈ I are flipped, i.e. space(t′, i) = flip(space(t, i)).
The isomorphism that flip applies to each of the indices i ∈ I is such that flipping two indices that are afterwards contracted within an @tensor contraction will yield the same result as without flipping those indices first. However, flip is not involutory, i.e. flip(flip(t, I), I) != t in general. To obtain the original tensor, one can use the inv keyword, i.e. it holds that flip(flip(t, I), I; inv=true) == t.
Inserting and removing unit spaces
The next set of functions add or remove a trivial tensor product factor at a specified index position, without affecting any other indices. We distinguish between insertleftunit, which inserts a unit index before index i (the unit index becoming index i), and insertrightunit, which inserts after index i (the unit index becoming index i + 1); removeunit undoes either insertion.
For tensors t with UnitStyle(sectortype(t)) = SimpleUnit(), the only relevant difference between insertleftunit(t, i + 1) and insertrightunit(t, i) is that insertleftunit(t, numout(t) + 1) inserts the unit index as first index in the domain, whereas insertrightunit(t, numout(t)) will insert the unit index as last index in the codomain.
Passing Val(i) instead of an integer i for the position may improve type stability.
TensorKit.insertleftunit — Method
insertleftunit(
tsrc::AbstractTensorMap, i = numind(t) + 1;
conj = false, dual = false, copy = false
) -> tdstInsert a trivial vector space, isomorphic to the underlying field, at position i, which can be specified as an Int or as Val(i) for improved type stability. More specifically, adds a left monoidal unit or its dual. Insert a trivial vector space, isomorphic to the underlying field, before position i, which should satisfy 1 ≤ i ≤ numind(t) + 1 and can be specified as an Int or as Val(i) for improved type stability, More specifically, add a left monoidal unit (or its dual) of the space associated with index i. The new index appears at position i in the new tensor, namely in its codomain for 1 ≤ i ≤ numout(t) and in its domain otherwise. If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.
See also insertrightunit, removeunit.
TensorKit.insertrightunit — Method
insertrightunit(
tsrc::AbstractTensorMap, i = numind(t);
conj = false, dual = false, copy = false
) -> tdstInsert a trivial vector space, isomorphic to the underlying field, after position i, which should satisfy 0 ≤ i ≤ numind(t) and can be specified as an Int or as Val(i) for improved type stability, More specifically, add a right monoidal unit (or its dual) of the space associated with index i. The new index appears at position i+1 in the new tensor, namely in its codomain for 0 ≤ i ≤ numout(t) and in its domain otherwise.
If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.
See also insertleftunit, removeunit.
TensorKit.removeunit — Method
removeunit(tsrc::AbstractTensorMap, i; copy = false) -> tdstThis removes a trivial tensor product factor at position 1 ≤ i ≤ N, where i can be specified as an Int or as Val(i) for improved type stability. For this to work, that factor has to be isomorphic to the field of scalars.
If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.
This operation undoes the work of insertleftunit and insertrightunit.
Index rearrangements
These operations reorder indices and/or move them between domain and codomain by applying the transposing or braiding isomorphisms of the underlying category. They form a hierarchy from most general to most restricted:
braidis the most general: it accepts any permutation and requires alevelsargument — a tuple of heights, one per index — that determines whether each index crosses over or under the others it has to pass.permuteis a simpler interface for sector types with a symmetric braiding (BraidingStyle(I) isa SymmetricBraiding), where over- and under-crossings are equivalent andlevelsis therefore not needed.transposeis restricted to cyclic permutations (indices do not cross).repartitiononly moves the codomain/domain boundary without reordering the indices at all.
For plain tensors (sectortype(t) == Trivial), permute and braid act like permutedims on the underlying array:
julia> V = ℂ^2;julia> t = randn(V ⊗ V ← V ⊗ V);julia> ta = convert(Array, t);julia> t′ = permute(t, ((4, 2, 3), (1,)));julia> convert(Array, t′) ≈ permutedims(ta, (4, 2, 3, 1))true
TensorKit.braid — Method
braid(
tsrc, (p₁, p₂)::Index2Tuple, levels::IndexTuple; copy = false,
backend = DefaultBackend(), allocator = DefaultAllocator()
) -> tdst::TensorMapReturn tensor tdst obtained by braiding the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively. Here, levels is a tuple of length numind(tsrc) that assigns a level or height to the indices of tsrc, which determines whether they will braid over or under any other index with which they have to change places.
If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made. Optionally specify a backend and allocator for the underlying array operation.
See also braid! for writing into an existing destination.
TensorKit.braid! — Function
braid!(tdst, tsrc, (p₁, p₂)::Index2Tuple, levels::IndexTuple, α = 1, β = 0, [backend], [allocator]) -> tdstCompute tdst = β * tdst + α * braid(tsrc, (p₁, p₂), levels), writing the result into tdst. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively. Here, levels is a tuple of length numind(tsrc) that assigns a level or height to the indices of tsrc, which determines whether they will braid over or under any other index with which they have to change places. Optionally specify a backend and allocator for the underlying array operation.
See also braid for creating a new tensor.
TensorKit.permute — Method
permute(
tsrc, (p₁, p₂)::Index2Tuple; copy = false,
backend = DefaultBackend(), allocator = DefaultAllocator()
) -> tdst::TensorMapReturn tensor tdst obtained by permuting the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively.
If copy = false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made. Optionally specify a backend and allocator for the underlying array operation.
See also permute! for writing into an existing destination.
Base.permute! — Method
permute!(tdst, tsrc, (p₁, p₂)::Index2Tuple, α = 1, β = 0, [backend], [allocator]) -> tdstCompute tdst = β * tdst + α * permute(tsrc, (p₁, p₂)), writing the result into tdst. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively. Optionally specify a backend and allocator for the underlying array operation.
See also permute for creating a new tensor.
Base.transpose — Method
transpose(
tsrc, (p₁, p₂)::Index2Tuple; copy = false,
backend = DefaultBackend(), allocator = DefaultAllocator()
) -> tdst::TensorMapReturn tensor tdst obtained by transposing the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively. The new index positions should be attainable without any indices crossing each other, i.e., the permutation (p₁..., reverse(p₂)...) should constitute a cyclic permutation of (codomainind(tsrc)..., reverse(domainind(tsrc))...).
If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made. Optionally specify a backend and allocator for the underlying array operation.
See also transpose! for writing into an existing destination.
LinearAlgebra.transpose! — Function
transpose!(tdst, tsrc, (p₁, p₂)::Index2Tuple, α = 1, β = 0, [backend], [allocator]) -> tdstCompute tdst = β * tdst + α * transpose(tsrc, (p₁, p₂)), writing the result into tdst. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively. The new index positions should be attainable without any indices crossing each other, i.e., the permutation (p₁..., reverse(p₂)...) should constitute a cyclic permutation of (codomainind(tsrc)..., reverse(domainind(tsrc))...). Optionally specify a backend and allocator for the underlying array operation.
See also transpose for creating a new tensor.
TensorKit.repartition — Method
repartition(
tsrc, N₁::Int, N₂::Int = numind(tsrc) - N₁; copy = false,
backend = DefaultBackend(), allocator = DefaultAllocator()
) -> tdstReturn tensor tdst obtained by repartitioning the indices of tsrc. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of tsrc, respectively.
If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made. Optionally specify a backend and allocator for the underlying array operation.
See also repartition! for writing into an existing destination.
TensorKit.repartition! — Function
repartition!(tdst, tsrc, α = 1, β = 0, [backend], [allocator]) -> tdstCompute tdst = β * tdst + α * repartition(tsrc), writing the result into tdst. This is a special case of transpose! that only changes the partition of indices between codomain and domain, without changing their cyclic order. Optionally specify a backend and allocator for the underlying array operation.
See also repartition for creating a new tensor.
Fusing and splitting indices
There is no dedicated functionality for fusing or splitting indices. In the general case there is no canonical embedding of V1 ⊗ V2 into the fused space V = fuse(V1 ⊗ V2): any two such embeddings differ by a basis transform, i.e. there is a gauge freedom. TensorKit resolves this by requiring the user to construct an explicit isomorphism — the fuser — and contract it with the tensor. One particular isomorphism can be constructed using the `unitary function. It preserves norms and inner products, and has an inverse given by its adjoint. For a plain tensor (sectortype(t) == Trivial), applying this particular unitary is equivalent to reshape on the underlying array.
Fusing index i and j = i+1 of a tensor t is then accomplished as
The resulting unitary is a dense TensorMap, and this fusion and splitting approach is not optimized for maximal performance. However, because many tensor operations including tensor factorizations (SVD, QR, etc.) can be applied without needing any fusion, we do not expect fusion and splitting to be an essential part of performance critical parts of typical tensor algorithms.