From 7141718c66f00ac851a8289384319e6f6e7ff148 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Fri, 13 Dec 2024 19:30:00 +0000 Subject: [PATCH] build based on 7fb72ec --- previews/PR187/.documenter-siteinfo.json | 2 +- previews/PR187/index.html | 2 +- previews/PR187/index/index.html | 2 +- previews/PR187/lib/sectors/index.html | 24 +- previews/PR187/lib/spaces/index.html | 29 +- previews/PR187/lib/tensors/index.html | 100 +-- previews/PR187/man/categories/index.html | 2 +- previews/PR187/man/intro/index.html | 2 +- previews/PR187/man/sectors/index.html | 4 +- previews/PR187/man/spaces/index.html | 4 +- previews/PR187/man/tensors/index.html | 848 +++++++++++------------ previews/PR187/man/tutorial/index.html | 802 ++++++++++----------- previews/PR187/objects.inv | Bin 4663 -> 4719 bytes previews/PR187/search_index.js | 2 +- 14 files changed, 912 insertions(+), 911 deletions(-) diff --git a/previews/PR187/.documenter-siteinfo.json b/previews/PR187/.documenter-siteinfo.json index f3b6e76b..563eaeb0 100644 --- a/previews/PR187/.documenter-siteinfo.json +++ b/previews/PR187/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.2","generation_timestamp":"2024-12-12T02:03:20","documenter_version":"1.8.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.2","generation_timestamp":"2024-12-13T19:29:53","documenter_version":"1.8.0"}} \ No newline at end of file diff --git a/previews/PR187/index.html b/previews/PR187/index.html index dbca188f..42805c9e 100644 --- a/previews/PR187/index.html +++ b/previews/PR187/index.html @@ -1,2 +1,2 @@ -Home · TensorKit.jl

TensorKit.jl

A Julia package for large-scale tensor computations, with a hint of category theory.

Package summary

TensorKit.jl aims to be a generic package for working with tensors as they appear throughout the physical sciences. TensorKit implements a parametric type Tensor (which is actually a specific case of the type TensorMap) and defines for these types a number of vector space operations (scalar multiplication, addition, norms and inner products), index operations (permutations) and linear algebra operations (multiplication, factorizations). Finally, tensor contractions can be performed using the @tensor macro from TensorOperations.jl.

Currently, most effort is oriented towards tensors as they appear in the context of quantum many body physics and in particular the field of tensor networks. Such tensors often have large dimensions and take on a specific structure when symmetries are present. To deal with generic symmetries, we employ notations and concepts from category theory all the way down to the definition of a tensor.

At the same time, TensorKit.jl focusses on computational efficiency and performance. The underlying storage of a tensor's data can be any DenseArray. Currently, certain operations are already multithreaded, either by distributing the different blocks in case of a structured tensor (i.e. with symmetries) or by using multithreading provided by the package Strided.jl. In the future, we also plan to investigate using CuArrays as underlying storage for the tensors data, so as to leverage GPUs for the different operations defined on tensors.

Contents of the manual

Library outline

+Home · TensorKit.jl

TensorKit.jl

A Julia package for large-scale tensor computations, with a hint of category theory.

Package summary

TensorKit.jl aims to be a generic package for working with tensors as they appear throughout the physical sciences. TensorKit implements a parametric type Tensor (which is actually a specific case of the type TensorMap) and defines for these types a number of vector space operations (scalar multiplication, addition, norms and inner products), index operations (permutations) and linear algebra operations (multiplication, factorizations). Finally, tensor contractions can be performed using the @tensor macro from TensorOperations.jl.

Currently, most effort is oriented towards tensors as they appear in the context of quantum many body physics and in particular the field of tensor networks. Such tensors often have large dimensions and take on a specific structure when symmetries are present. To deal with generic symmetries, we employ notations and concepts from category theory all the way down to the definition of a tensor.

At the same time, TensorKit.jl focusses on computational efficiency and performance. The underlying storage of a tensor's data can be any DenseArray. Currently, certain operations are already multithreaded, either by distributing the different blocks in case of a structured tensor (i.e. with symmetries) or by using multithreading provided by the package Strided.jl. In the future, we also plan to investigate using CuArrays as underlying storage for the tensors data, so as to leverage GPUs for the different operations defined on tensors.

Contents of the manual

Library outline

diff --git a/previews/PR187/index/index.html b/previews/PR187/index/index.html index d3b90d8d..ec96aa5b 100644 --- a/previews/PR187/index/index.html +++ b/previews/PR187/index/index.html @@ -1,2 +1,2 @@ -Index · TensorKit.jl

Index

+Index · TensorKit.jl

Index

diff --git a/previews/PR187/lib/sectors/index.html b/previews/PR187/lib/sectors/index.html index 15f018c1..e8833fa2 100644 --- a/previews/PR187/lib/sectors/index.html +++ b/previews/PR187/lib/sectors/index.html @@ -34,26 +34,26 @@ SU{N} where N const SU₂ = SU{2} ProductGroup

Specific methods:

TensorKitSectors.:×Function
×(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}
-times(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}

Construct the direct product of a (list of) groups.

source

Methods for defining and generating fusion trees

TensorKit.FusionTreeType
struct FusionTree{I, N, M, L}

Represents a fusion tree of sectors of type I<:Sector, fusing (or splitting) N uncoupled sectors to a coupled sector. It actually represents a splitting tree, but fusion tree is a more common term.

Fields

  • uncoupled::NTuple{N,I}: the uncoupled sectors coming out of the splitting tree, before the possible 𝑍 isomorphism (see isdual).
  • coupled::I: the coupled sector.
  • isdual::NTuple{N,Bool}: indicates whether a 𝑍 isomorphism is present (true) or not (false) for each uncoupled sector.
  • innerlines::NTuple{M,I}: the labels of the M=max(0, N-2) inner lines of the splitting tree.
  • vertices::NTuple{L,Int}: the integer values of the L=max(0, N-1) vertices of the splitting tree. If FusionStyle(I) isa MultiplicityFreeFusion, then vertices is simply equal to the constant value ntuple(n->1, L).
source
TensorKit.fusiontreesMethod
fusiontrees(uncoupled::NTuple{N,I}[,
+times(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}

Construct the direct product of a (list of) groups.

source

Methods for defining and generating fusion trees

TensorKit.FusionTreeType
struct FusionTree{I, N, M, L}

Represents a fusion tree of sectors of type I<:Sector, fusing (or splitting) N uncoupled sectors to a coupled sector. It actually represents a splitting tree, but fusion tree is a more common term.

Fields

  • uncoupled::NTuple{N,I}: the uncoupled sectors coming out of the splitting tree, before the possible 𝑍 isomorphism (see isdual).
  • coupled::I: the coupled sector.
  • isdual::NTuple{N,Bool}: indicates whether a 𝑍 isomorphism is present (true) or not (false) for each uncoupled sector.
  • innerlines::NTuple{M,I}: the labels of the M=max(0, N-2) inner lines of the splitting tree.
  • vertices::NTuple{L,Int}: the integer values of the L=max(0, N-1) vertices of the splitting tree. If FusionStyle(I) isa MultiplicityFreeFusion, then vertices is simply equal to the constant value ntuple(n->1, L).
source
TensorKit.fusiontreesMethod
fusiontrees(uncoupled::NTuple{N,I}[,
     coupled::I=one(I)[, isdual::NTuple{N,Bool}=ntuple(n -> false, length(uncoupled))]])
-    where {N,I<:Sector} -> FusionTreeIterator{I,N,I}

Return an iterator over all fusion trees with a given coupled sector label coupled and uncoupled sector labels and isomorphisms uncoupled and isdual respectively.

source

Methods for manipulating fusion trees

For manipulating single fusion trees, the following internal methods are defined:

TensorKit.insertatFunction
insertat(f::FusionTree{I, N₁}, i::Int, f₂::FusionTree{I, N₂})
--> <:AbstractDict{<:FusionTree{I, N₁+N₂-1}, <:Number}

Attach a fusion tree f₂ to the uncoupled leg i of the fusion tree f₁ and bring it into a linear combination of fusion trees in standard form. This requires that f₂.coupled == f₁.uncoupled[i] and f₁.isdual[i] == false.

source
TensorKit.splitFunction
split(f::FusionTree{I, N}, M::Int)
--> (::FusionTree{I, M}, ::FusionTree{I, N-M+1})

Split a fusion tree into two. The first tree has as uncoupled sectors the first M uncoupled sectors of the input tree f, whereas its coupled sector corresponds to the internal sector between uncoupled sectors M and M+1 of the original tree f. The second tree has as first uncoupled sector that same internal sector of f, followed by remaining N-M uncoupled sectors of f. It couples to the same sector as f. This operation is the inverse of insertat in the sense that if f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁).

source
TensorKit.mergeFunction
merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = 1)
--> <:AbstractDict{<:FusionTree{I, N₁+N₂}, <:Number}

Merge two fusion trees together to a linear combination of fusion trees whose uncoupled sectors are those of f₁ followed by those of f₂, and where the two coupled sectors of f₁ and f₂ are further fused to c. In case of FusionStyle(I) == GenericFusion(), also a degeneracy label μ for the fusion of the coupled sectors of f₁ and f₂ to c needs to be specified.

source
TensorKit.elementary_traceFunction
elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} -> <:AbstractDict{FusionTree{I,N-2}, <:Number}

Perform an elementary trace of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.

source
TensorKit.planar_traceMethod
planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}
-    -> <:AbstractDict{FusionTree{I,N-2*N₃}, <:Number}

Perform a planar trace of the uncoupled indices of the fusion tree f at q1 with those at q2, where q1[i] is connected to q2[i] for all i. The result is returned as a dictionary of output trees and corresponding coefficients.

source
TensorKit.artin_braidFunction
artin_braid(f::FusionTree, i; inv::Bool = false) -> <:AbstractDict{typeof(f), <:Number}

Perform an elementary braid (Artin generator) of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.

The keyword inv determines whether index i will braid above or below index i+1, i.e. applying artin_braid(f′, i; inv = true) to all the outputs f′ of artin_braid(f, i; inv = false) and collecting the results should yield a single fusion tree with non-zero coefficient, namely f with coefficient 1. This keyword has no effect if BraidingStyle(sectortype(f)) isa SymmetricBraiding.

source
TensorKit.braidMethod
braid(f::FusionTree{<:Sector, N}, levels::NTuple{N, Int}, p::NTuple{N, Int})
--> <:AbstractDict{typeof(t), <:Number}

Perform a braiding of the uncoupled indices of the fusion tree f and return the result as a <:AbstractDict of output trees and corresponding coefficients. The braiding is determined by specifying that the new sector at position k corresponds to the sector that was originally at the position i = p[k], and assigning to every index i of the original fusion tree a distinct level or depth levels[i]. This permutation is then decomposed into elementary swaps between neighbouring indices, where the swaps are applied as braids such that if i and j cross, $τ_{i,j}$ is applied if levels[i] < levels[j] and $τ_{j,i}^{-1}$ if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.

source
TensorKit.permuteMethod
permute(f::FusionTree, p::NTuple{N, Int}) -> <:AbstractDict{typeof(t), <:Number}

Perform a permutation of the uncoupled indices of the fusion tree f and returns the result as a <:AbstractDict of output trees and corresponding coefficients; this requires that BraidingStyle(sectortype(f)) isa SymmetricBraiding.

source

These can be composed to implement elementary manipulations of fusion-splitting tree pairs, according to the following methods

# TODO: add documentation for the following methods
+    where {N,I<:Sector} -> FusionTreeIterator{I,N,I}

Return an iterator over all fusion trees with a given coupled sector label coupled and uncoupled sector labels and isomorphisms uncoupled and isdual respectively.

source

Methods for manipulating fusion trees

For manipulating single fusion trees, the following internal methods are defined:

TensorKit.insertatFunction
insertat(f::FusionTree{I, N₁}, i::Int, f₂::FusionTree{I, N₂})
+-> <:AbstractDict{<:FusionTree{I, N₁+N₂-1}, <:Number}

Attach a fusion tree f₂ to the uncoupled leg i of the fusion tree f₁ and bring it into a linear combination of fusion trees in standard form. This requires that f₂.coupled == f₁.uncoupled[i] and f₁.isdual[i] == false.

source
TensorKit.splitFunction
split(f::FusionTree{I, N}, M::Int)
+-> (::FusionTree{I, M}, ::FusionTree{I, N-M+1})

Split a fusion tree into two. The first tree has as uncoupled sectors the first M uncoupled sectors of the input tree f, whereas its coupled sector corresponds to the internal sector between uncoupled sectors M and M+1 of the original tree f. The second tree has as first uncoupled sector that same internal sector of f, followed by remaining N-M uncoupled sectors of f. It couples to the same sector as f. This operation is the inverse of insertat in the sense that if f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁).

source
TensorKit.mergeFunction
merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = 1)
+-> <:AbstractDict{<:FusionTree{I, N₁+N₂}, <:Number}

Merge two fusion trees together to a linear combination of fusion trees whose uncoupled sectors are those of f₁ followed by those of f₂, and where the two coupled sectors of f₁ and f₂ are further fused to c. In case of FusionStyle(I) == GenericFusion(), also a degeneracy label μ for the fusion of the coupled sectors of f₁ and f₂ to c needs to be specified.

source
TensorKit.elementary_traceFunction
elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} -> <:AbstractDict{FusionTree{I,N-2}, <:Number}

Perform an elementary trace of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.

source
TensorKit.planar_traceMethod
planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}
+    -> <:AbstractDict{FusionTree{I,N-2*N₃}, <:Number}

Perform a planar trace of the uncoupled indices of the fusion tree f at q1 with those at q2, where q1[i] is connected to q2[i] for all i. The result is returned as a dictionary of output trees and corresponding coefficients.

source
TensorKit.artin_braidFunction
artin_braid(f::FusionTree, i; inv::Bool = false) -> <:AbstractDict{typeof(f), <:Number}

Perform an elementary braid (Artin generator) of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.

The keyword inv determines whether index i will braid above or below index i+1, i.e. applying artin_braid(f′, i; inv = true) to all the outputs f′ of artin_braid(f, i; inv = false) and collecting the results should yield a single fusion tree with non-zero coefficient, namely f with coefficient 1. This keyword has no effect if BraidingStyle(sectortype(f)) isa SymmetricBraiding.

source
TensorKit.braidMethod
braid(f::FusionTree{<:Sector, N}, levels::NTuple{N, Int}, p::NTuple{N, Int})
+-> <:AbstractDict{typeof(t), <:Number}

Perform a braiding of the uncoupled indices of the fusion tree f and return the result as a <:AbstractDict of output trees and corresponding coefficients. The braiding is determined by specifying that the new sector at position k corresponds to the sector that was originally at the position i = p[k], and assigning to every index i of the original fusion tree a distinct level or depth levels[i]. This permutation is then decomposed into elementary swaps between neighbouring indices, where the swaps are applied as braids such that if i and j cross, $τ_{i,j}$ is applied if levels[i] < levels[j] and $τ_{j,i}^{-1}$ if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.

source
TensorKit.permuteMethod
permute(f::FusionTree, p::NTuple{N, Int}) -> <:AbstractDict{typeof(t), <:Number}

Perform a permutation of the uncoupled indices of the fusion tree f and returns the result as a <:AbstractDict of output trees and corresponding coefficients; this requires that BraidingStyle(sectortype(f)) isa SymmetricBraiding.

source

These can be composed to implement elementary manipulations of fusion-splitting tree pairs, according to the following methods

# TODO: add documentation for the following methods
 TensorKit.bendright
 TensorKit.bendleft
 TensorKit.foldright
 TensorKit.foldleft
 TensorKit.cycleclockwise
 TensorKit.cycleanticlockwise

Finally, these are used to define large manipulations of fusion-splitting tree pairs, which are then used in the index manipulation of AbstractTensorMap objects. The following methods defined on fusion splitting tree pairs have an associated definition for tensors.

TensorKit.repartitionFunction
repartition(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, N::Int) where {I, N₁, N₂}
--> <:AbstractDict{Tuple{FusionTree{I, N}, FusionTree{I, N₁+N₂-N}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (f₁) and incoming sectors (f₂) respectively (with identical coupled sector f₁.coupled == f₂.coupled). Computes new trees and corresponding coefficients obtained from repartitioning the tree by bending incoming to outgoing sectors (or vice versa) in order to have N outgoing sectors.

source
repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}
-    -> tdst::AbstractTensorMap{S,N₁,N₂}

Return tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

To repartition into an existing destination, see repartition!.

source
Base.transposeMethod
transpose(f₁::FusionTree{I}, f₂::FusionTree{I},
+-> <:AbstractDict{Tuple{FusionTree{I, N}, FusionTree{I, N₁+N₂-N}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (f₁) and incoming sectors (f₂) respectively (with identical coupled sector f₁.coupled == f₂.coupled). Computes new trees and corresponding coefficients obtained from repartitioning the tree by bending incoming to outgoing sectors (or vice versa) in order to have N outgoing sectors.

source
repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}
+    -> tdst::AbstractTensorMap{S,N₁,N₂}

Return tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

To repartition into an existing destination, see repartition!.

source
Base.transposeMethod
transpose(f₁::FusionTree{I}, f₂::FusionTree{I},
         p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}
--> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.

source
TensorKit.braidMethod
braid(f₁::FusionTree{I}, f₂::FusionTree{I},
+-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.

source
TensorKit.braidMethod
braid(f₁::FusionTree{I}, f₂::FusionTree{I},
         levels1::IndexTuple, levels2::IndexTuple,
         p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂}
--> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a fusion-splitting tree pair that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the splitting tree f₁ and fusion tree f₂, such that the incoming sectors f₂.uncoupled are fused to f₁.coupled == f₂.coupled and then to the outgoing sectors f₁.uncoupled. Compute new trees and corresponding coefficients obtained from repartitioning and braiding the tree such that sectors p1 become outgoing and sectors p2 become incoming. The uncoupled indices in splitting tree f₁ and fusion tree f₂ have levels (or depths) levels1 and levels2 respectively, which determines how indices braid. In particular, if i and j cross, $τ_{i,j}$ is applied if levels[i] < levels[j] and $τ_{j,i}^{-1}$ if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.

source
TensorKit.permuteMethod
permute(f₁::FusionTree{I}, f₂::FusionTree{I},
+-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a fusion-splitting tree pair that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the splitting tree f₁ and fusion tree f₂, such that the incoming sectors f₂.uncoupled are fused to f₁.coupled == f₂.coupled and then to the outgoing sectors f₁.uncoupled. Compute new trees and corresponding coefficients obtained from repartitioning and braiding the tree such that sectors p1 become outgoing and sectors p2 become incoming. The uncoupled indices in splitting tree f₁ and fusion tree f₂ have levels (or depths) levels1 and levels2 respectively, which determines how indices braid. In particular, if i and j cross, $τ_{i,j}$ is applied if levels[i] < levels[j] and $τ_{j,i}^{-1}$ if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.

source
TensorKit.permuteMethod
permute(f₁::FusionTree{I}, f₂::FusionTree{I},
         p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}
--> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.

source
+-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}

Input is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.

source diff --git a/previews/PR187/lib/spaces/index.html b/previews/PR187/lib/spaces/index.html index 1b77ffd0..58f54372 100644 --- a/previews/PR187/lib/spaces/index.html +++ b/previews/PR187/lib/spaces/index.html @@ -1,12 +1,12 @@ -Vector spaces · TensorKit.jl

Vector spaces

Type hierarchy

The following types are defined to characterise vector spaces and their properties:

TensorKit.FieldType
abstract type Field end

Abstract type at the top of the type hierarchy for denoting fields over which vector spaces (or more generally, linear categories) can be defined. Two common fields are and , representing the field of real or complex numbers respectively.

source
TensorKit.VectorSpaceType
abstract type VectorSpace end

Abstract type at the top of the type hierarchy for denoting vector spaces, or, more accurately, 𝕜-linear categories. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories.

source
TensorKit.ElementarySpaceType
abstract type ElementarySpace <: VectorSpace end

Elementary finite-dimensional vector space over a field that can be used as the index space corresponding to the indices of a tensor. ElementarySpace is a supertype for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.

Every elementary vector space should respond to the methods conj and dual, returning the complex conjugate space and the dual space respectively. The complex conjugate of the dual space is obtained as dual(conj(V)) === conj(dual(V)). These different spaces should be of the same type, so that a tensor can be defined as an element of a homogeneous tensor product of these spaces.

source
TensorKit.GeneralSpaceType
struct GeneralSpace{𝔽} <: ElementarySpace

A finite-dimensional space over an arbitrary field 𝔽 without additional structure. It is thus characterized by its dimension, and whether or not it is the dual and/or conjugate space. For a real field 𝔽, the space and its conjugate are the same.

source
TensorKit.CartesianSpaceType
struct CartesianSpace <: ElementarySpace

A real Euclidean space ℝ^d, which is therefore self-dual. CartesianSpace has no additonal structure and is completely characterised by its dimension d. This is the vector space that is implicitly assumed in most of matrix algebra.

source
TensorKit.ComplexSpaceType
struct ComplexSpace <: ElementarySpace

A standard complex vector space ℂ^d with Euclidean inner product and no additional structure. It is completely characterised by its dimension and whether its the normal space or its dual (which is canonically isomorphic to the conjugate space).

source
TensorKit.GradedSpaceType
struct GradedSpace{I<:Sector, D} <: ElementarySpace
+Vector spaces · TensorKit.jl

Vector spaces

Type hierarchy

The following types are defined to characterise vector spaces and their properties:

TensorKit.FieldType
abstract type Field end

Abstract type at the top of the type hierarchy for denoting fields over which vector spaces (or more generally, linear categories) can be defined. Two common fields are and , representing the field of real or complex numbers respectively.

source
TensorKit.VectorSpaceType
abstract type VectorSpace end

Abstract type at the top of the type hierarchy for denoting vector spaces, or, more accurately, 𝕜-linear categories. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories.

source
TensorKit.ElementarySpaceType
abstract type ElementarySpace <: VectorSpace end

Elementary finite-dimensional vector space over a field that can be used as the index space corresponding to the indices of a tensor. ElementarySpace is a supertype for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.

Every elementary vector space should respond to the methods conj and dual, returning the complex conjugate space and the dual space respectively. The complex conjugate of the dual space is obtained as dual(conj(V)) === conj(dual(V)). These different spaces should be of the same type, so that a tensor can be defined as an element of a homogeneous tensor product of these spaces.

source
TensorKit.GeneralSpaceType
struct GeneralSpace{𝔽} <: ElementarySpace

A finite-dimensional space over an arbitrary field 𝔽 without additional structure. It is thus characterized by its dimension, and whether or not it is the dual and/or conjugate space. For a real field 𝔽, the space and its conjugate are the same.

source
TensorKit.CartesianSpaceType
struct CartesianSpace <: ElementarySpace

A real Euclidean space ℝ^d, which is therefore self-dual. CartesianSpace has no additonal structure and is completely characterised by its dimension d. This is the vector space that is implicitly assumed in most of matrix algebra.

source
TensorKit.ComplexSpaceType
struct ComplexSpace <: ElementarySpace

A standard complex vector space ℂ^d with Euclidean inner product and no additional structure. It is completely characterised by its dimension and whether its the normal space or its dual (which is canonically isomorphic to the conjugate space).

source
TensorKit.GradedSpaceType
struct GradedSpace{I<:Sector, D} <: ElementarySpace
     dims::D
     dual::Bool
-end

A complex Euclidean space with a direct sum structure corresponding to labels in a set I, the objects of which have the structure of a monoid with respect to a monoidal product . In practice, we restrict the label set to be a set of superselection sectors of type I<:Sector, e.g. the set of distinct irreps of a finite or compact group, or the isomorphism classes of simple objects of a unitary and pivotal (pre-)fusion category.

Here dims represents the degeneracy or multiplicity of every sector.

The data structure D of dims will depend on the result Base.IteratorElsize(values(I)); if the result is of type HasLength or HasShape, dims will be stored in a NTuple{N,Int} with N = length(values(I)). This requires that a sector s::I can be transformed into an index via s == getindex(values(I), i) and i == findindex(values(I), s). If Base.IteratorElsize(values(I)) results IsInfinite() or SizeUnknown(), a SectorDict{I,Int} is used to store the non-zero degeneracy dimensions with the corresponding sector as key. The parameter D is hidden from the user and should typically be of no concern.

The concrete type GradedSpace{I,D} with correct D can be obtained as Vect[I], or if I == Irrep[G] for some G<:Group, as Rep[G].

source
TensorKit.CompositeSpaceType
abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end

Abstract type for composite spaces that are defined in terms of a number of elementary vector spaces of a homogeneous type S<:ElementarySpace.

source
TensorKit.ProductSpaceType
struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}

A ProductSpace is a tensor product space of N vector spaces of type S<:ElementarySpace. Only tensor products between ElementarySpace objects of the same type are allowed.

source
TensorKit.HomSpaceType
struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}
+end

A complex Euclidean space with a direct sum structure corresponding to labels in a set I, the objects of which have the structure of a monoid with respect to a monoidal product . In practice, we restrict the label set to be a set of superselection sectors of type I<:Sector, e.g. the set of distinct irreps of a finite or compact group, or the isomorphism classes of simple objects of a unitary and pivotal (pre-)fusion category.

Here dims represents the degeneracy or multiplicity of every sector.

The data structure D of dims will depend on the result Base.IteratorElsize(values(I)); if the result is of type HasLength or HasShape, dims will be stored in a NTuple{N,Int} with N = length(values(I)). This requires that a sector s::I can be transformed into an index via s == getindex(values(I), i) and i == findindex(values(I), s). If Base.IteratorElsize(values(I)) results IsInfinite() or SizeUnknown(), a SectorDict{I,Int} is used to store the non-zero degeneracy dimensions with the corresponding sector as key. The parameter D is hidden from the user and should typically be of no concern.

The concrete type GradedSpace{I,D} with correct D can be obtained as Vect[I], or if I == Irrep[G] for some G<:Group, as Rep[G].

source
TensorKit.CompositeSpaceType
abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end

Abstract type for composite spaces that are defined in terms of a number of elementary vector spaces of a homogeneous type S<:ElementarySpace.

source
TensorKit.ProductSpaceType
struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}

A ProductSpace is a tensor product space of N vector spaces of type S<:ElementarySpace. Only tensor products between ElementarySpace objects of the same type are allowed.

source
TensorKit.HomSpaceType
struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}
     codomain::P1
     domain::P2
-end

Represents the linear space of morphisms with codomain of type P1 and domain of type P2. Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct.

source

together with the following specific types for encoding the inner product structure of a space:

TensorKit.InnerProductStyleType
InnerProductStyle(V::VectorSpace) -> ::InnerProductStyle
-InnerProductStyle(S::Type{<:VectorSpace}) -> ::InnerProductStyle

Return the type of inner product for vector spaces, which can be either

  • NoInnerProduct(): no mapping from dual(V) to conj(V), i.e. no metric
  • subtype of HasInnerProduct: a metric exists, but no further support is implemented.
  • EuclideanInnerProduct(): the metric is the identity, such that dual and conjugate spaces are isomorphic.
source

Useful constants

The following constants are defined to easily create the concrete type of GradedSpace associated with a given type of sector.

TensorKit.VectConstant
const Vect

A constant of a singleton type used as Vect[I] with I<:Sector a type of sector, to construct or obtain the concrete type GradedSpace{I,D} instances without having to specify D.

source
TensorKit.RepConstant
const Rep

A constant of a singleton type used as Rep[G] with G<:Group a type of group, to construct or obtain the concrete type GradedSpace{Irrep[G],D} instances without having to specify D. Note that Rep[G] == Vect[Irrep[G]].

See also Irrep and Vect.

source

In this respect, there are also a number of type aliases for the GradedSpace types associated with the most common sectors, namely

const ZNSpace{N} = Vect[ZNIrrep{N}]
+end

Represents the linear space of morphisms with codomain of type P1 and domain of type P2. Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct.

source

together with the following specific types for encoding the inner product structure of a space:

TensorKit.InnerProductStyleType
InnerProductStyle(V::VectorSpace) -> ::InnerProductStyle
+InnerProductStyle(S::Type{<:VectorSpace}) -> ::InnerProductStyle

Return the type of inner product for vector spaces, which can be either

  • NoInnerProduct(): no mapping from dual(V) to conj(V), i.e. no metric
  • subtype of HasInnerProduct: a metric exists, but no further support is implemented.
  • EuclideanInnerProduct(): the metric is the identity, such that dual and conjugate spaces are isomorphic.
source

Useful constants

The following constants are defined to easily create the concrete type of GradedSpace associated with a given type of sector.

TensorKit.VectConstant
const Vect

A constant of a singleton type used as Vect[I] with I<:Sector a type of sector, to construct or obtain the concrete type GradedSpace{I,D} instances without having to specify D.

source
TensorKit.RepConstant
const Rep

A constant of a singleton type used as Rep[G] with G<:Group a type of group, to construct or obtain the concrete type GradedSpace{Irrep[G],D} instances without having to specify D. Note that Rep[G] == Vect[Irrep[G]].

See also Irrep and Vect.

source

In this respect, there are also a number of type aliases for the GradedSpace types associated with the most common sectors, namely

const ZNSpace{N} = Vect[ZNIrrep{N}]
 const Z2Space = ZNSpace{2}
 const Z3Space = ZNSpace{3}
 const Z4Space = ZNSpace{4}
@@ -20,14 +20,13 @@
 const ℤ₄Space = Z4Space
 const U₁Space = U1Space
 const CU₁Space = CU1Space
-const SU₂Space = SU2Space

Methods

Methods often apply similar to e.g. spaces and corresponding tensors or tensor maps, e.g.:

TensorKit.fieldFunction
field(V::VectorSpace) -> Field

Return the field type over which a vector space is defined.

source
TensorKit.sectortypeFunction
sectortype(a) -> Type{<:Sector}

Return the type of sector over which object a (e.g. a representation space or a tensor) is defined. Also works in type domain.

source
sectortype(::AbstractTensorMap) -> Type{I<:Sector}
-sectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}

Return the type of sector I of a tensor.

source
TensorKit.hassectorFunction
hassector(V::VectorSpace, a::Sector) -> Bool

Return whether a vector space V has a subspace corresponding to sector a with non-zero dimension, i.e. dim(V, a) > 0.

source
TensorKitSectors.dimMethod
dim(V::ElementarySpace, s::Sector) -> Int

Return the degeneracy dimension corresponding to the sector s of the vector space V.

source
TensorKit.reduceddimFunction
reduceddim(V::ElementarySpace) -> Int

Return the sum of all degeneracy dimensions of the vector space V.

source
TensorKitSectors.dimMethod
dim(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}
--> Int

Return the total degeneracy dimension corresponding to a tuple of sectors for each of the spaces in the tensor product, obtained as prod(dims(P, s))`.

source
TensorKitSectors.dimMethod
dim(W::HomSpace)

Return the total dimension of a HomSpace, i.e. the number of linearly independent morphisms that can be constructed within this space.

source
TensorKit.dimsFunction
dims(::ProductSpace{S, N}) -> Dims{N} = NTuple{N, Int}

Return the dimensions of the spaces in the tensor product space as a tuple of integers.

source
dims(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}
--> Dims{N} = NTuple{N, Int}

Return the degeneracy dimensions corresponding to a tuple of sectors s for each of the spaces in the tensor product P.

source
TensorKit.blocksectorsMethod
blocksectors(P::ProductSpace)

Return an iterator over the different unique coupled sector labels, i.e. the different fusion outputs that can be obtained by fusing the sectors present in the different spaces that make up the ProductSpace instance.

source
TensorKit.blocksectorsMethod
blocksectors(W::HomSpace)

Return an iterator over the different unique coupled sector labels, i.e. the intersection of the different fusion outputs that can be obtained by fusing the sectors present in the domain, as well as from the codomain.

See also hasblock.

source
TensorKit.hasblockFunction
hasblock(P::ProductSpace, c::Sector)

Query whether a coupled sector c appears with nonzero dimension in P, i.e. whether blockdim(P, c) > 0.

See also blockdim and blocksectors.

source
hasblock(W::HomSpace, c::Sector)

Query whether a coupled sector c appears in both the codomain and domain of W.

See also blocksectors.

source
hasblock(t::AbstractTensorMap, c::Sector) -> Bool

Verify whether a tensor has a block corresponding to a coupled sector c.

source
TensorKit.blockdimFunction
blockdim(P::ProductSpace, c::Sector)

Return the total dimension of a coupled sector c in the product space, by summing over all dim(P, s) for all tuples of sectors s::NTuple{N, <:Sector} that can fuse to c, counted with the correct multiplicity (i.e. number of ways in which s can fuse to c).

See also hasblock and blocksectors.

source
TensorKit.fusiontreesMethod
fusiontrees(P::ProductSpace, blocksector::Sector)

Return an iterator over all fusion trees that can be formed by fusing the sectors present in the different spaces that make up the ProductSpace instance into the coupled sector blocksector.

source

The following methods act specifically on ElementarySpace spaces:

TensorKit.isdualFunction
isdual(V::ElementarySpace) -> Bool

Return wether an ElementarySpace V is normal or rather a dual space. Always returns false for spaces where V == dual(V).

source
TensorKitSectors.dualFunction
dual(V::VectorSpace) -> VectorSpace

Return the dual space of V; also obtained via V'. This should satisfy dual(dual(V)) == V. It is assumed that typeof(V) == typeof(V').

source
Base.conjFunction
conj(V::S) where {S<:ElementarySpace} -> S

Return the conjugate space of V. This should satisfy conj(conj(V)) == V.

For field(V)==ℝ, conj(V) == V. It is assumed that typeof(V) == typeof(conj(V)).

source
TensorKit.flipFunction
flip(V::S) where {S<:ElementarySpace} -> S

Return a single vector space of type S that has the same value of isdual as dual(V), but yet is isomorphic to V rather than to dual(V). The spaces flip(V) and dual(V) only differ in the case of GradedSpace{I}.

source
TensorKit.:⊕Function
⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S
-oplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the direct sum sum of the spaces V₁, V₂, ... Note that all the individual spaces should have the same value for isdual, as otherwise the direct sum is not defined.

source
Base.zeroMethod
zero(V::S) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces.

source
Base.oneunitFunction
oneunit(V::S) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from one(V::S), which returns the empty product space ProductSpace{S,0}(()).

source
TensorKit.supremumFunction
supremum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)

Return the supremum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≿ V₁, V ≿ V₂, ... and no other W ≺ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.

source
TensorKit.infimumFunction
infimum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)

Return the infimum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≾ V₁, V ≾ V₂, ... and no other W ≻ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.

source

while the following also work on both ElementarySpace and ProductSpace

Base.oneMethod
one(::S) where {S<:ElementarySpace} -> ProductSpace{S, 0}
-one(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S, 0}

Return a tensor product of zero spaces of type S, i.e. this is the unit object under the tensor product operation, such that V ⊗ one(V) == V.

source
TensorKit.fuseFunction
fuse(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S
-fuse(P::ProductSpace{S}) where {S<:ElementarySpace} -> S

Return a single vector space of type S that is isomorphic to the fusion product of the individual spaces V₁, V₂, ..., or the spaces contained in P.

source
TensorKitSectors.:⊗Method
⊗(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S

Create a ProductSpace{S}(V₁, V₂, V₃...) representing the tensor product of several elementary vector spaces. For convience, Julia's regular multiplication operator * applied to vector spaces has the same effect.

The tensor product structure is preserved, see fuse for returning a single elementary space of type S that is isomorphic to this tensor product.

source
TensorKitSectors.:⊠Method
⊠(V₁::VectorSpace, V₂::VectorSpace)

Given two vector spaces V₁ and V₂ (ElementarySpace or ProductSpace), or thus, objects of corresponding fusion categories $C₁$ and $C₂$, $V₁ ⊠ V₂$ constructs the Deligne tensor product, an object in $C₁ ⊠ C₂$ which is the natural tensor product of those categories. In particular, the corresponding type of sectors (simple objects) is given by sectortype(V₁ ⊠ V₂) == sectortype(V₁) ⊠ sectortype(V₂) and can be thought of as a tuple of the individual sectors.

The Deligne tensor product also works in the type domain and for sectors and tensors. For group representations, we have Rep[G₁] ⊠ Rep[G₂] == Rep[G₁ × G₂], i.e. these are the natural representation spaces of the direct product of two groups.

source
TensorKit.ismonomorphicFunction
ismonomorphic(V₁::VectorSpace, V₂::VectorSpace)
-V₁ ≾ V₂

Return whether there exist monomorphisms from V₁ to V₂, i.e. 'injective' morphisms with left inverses.

source
TensorKit.isepimorphicFunction
isepimorphic(V₁::VectorSpace, V₂::VectorSpace)
-V₁ ≿ V₂

Return whether there exist epimorphisms from V₁ to V₂, i.e. 'surjective' morphisms with right inverses.

source
TensorKit.isisomorphicFunction
isisomorphic(V₁::VectorSpace, V₂::VectorSpace)
-V₁ ≅ V₂

Return if V₁ and V₂ are isomorphic, meaning that there exists isomorphisms from V₁ to V₂, i.e. morphisms with left and right inverses.

source
TensorKit.insertunitFunction
insertunit(P::ProductSpace, i::Int = length(P)+1; dual = false, conj = false)

For P::ProductSpace{S,N}, this adds an extra tensor product factor at position 1 <= i <= N+1 (last position by default) which is just the S-equivalent of the underlying field of scalars, i.e. oneunit(S). With the keyword arguments, one can choose to insert the conjugated or dual space instead, which are all isomorphic to the field of scalars.

This operation can be undone by removeunit.

source
insertunit(W::HomSpace, i::Int=ndims(W) + 1; conj=false, dual=false, preferdomain=false)

Insert a trivial vector space, isomorphic to the underlying field, at position i. Whenever i == numout(W), the ambiguity to determine whether this space is added in the domain or codomain is controlled by preferdomain.

source
insertunit(tsrc::AbstractTensorMap, i::Int=numind(t) + 1;
-           conj=false, dual=false, preferdomain=false, copy=false) -> tdst

Insert a trivial vector space, isomorphic to the underlying field, at position i. Whenever i == numout(W), the ambiguity to determine whether this space is added in the domain or codomain is controlled by preferdomain.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

source

There are also specific methods for HomSpace instances, that are used in determining the resuling HomSpace after applying certain tensor operations.

TensorKit.permuteMethod
permute(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})

Return the HomSpace obtained by permuting the indices of the domain and codomain of W according to the permutation p₁ and p₂ respectively.

source
TensorKit.selectMethod
select(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})

Return the HomSpace obtained by a selection from the domain and codomain of W according to the indices in p₁ and p₂ respectively.

source
TensorKit.composeMethod
compose(W::HomSpace, V::HomSpace)

Obtain the HomSpace that is obtained from composing the morphisms in W and V. For this to be possible, the domain of W must match the codomain of V.

source
+const SU₂Space = SU2Space

Methods

Methods often apply similar to e.g. spaces and corresponding tensors or tensor maps, e.g.:

TensorKit.fieldFunction
field(V::VectorSpace) -> Field

Return the field type over which a vector space is defined.

source
TensorKit.sectortypeFunction
sectortype(a) -> Type{<:Sector}

Return the type of sector over which object a (e.g. a representation space or a tensor) is defined. Also works in type domain.

source
sectortype(::AbstractTensorMap) -> Type{I<:Sector}
+sectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}

Return the type of sector I of a tensor.

source
TensorKit.sectorsFunction
sectors(V::ElementarySpace)

Return an iterator over the different sectors of V.

source
TensorKit.hassectorFunction
hassector(V::VectorSpace, a::Sector) -> Bool

Return whether a vector space V has a subspace corresponding to sector a with non-zero dimension, i.e. dim(V, a) > 0.

source
TensorKitSectors.dimMethod
dim(V::VectorSpace) -> Int

Return the total dimension of the vector space V as an Int.

source
TensorKitSectors.dimMethod
dim(V::ElementarySpace, s::Sector) -> Int

Return the degeneracy dimension corresponding to the sector s of the vector space V.

source
TensorKit.reduceddimFunction
reduceddim(V::ElementarySpace) -> Int

Return the sum of all degeneracy dimensions of the vector space V.

source
TensorKitSectors.dimMethod
dim(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}
+-> Int

Return the total degeneracy dimension corresponding to a tuple of sectors for each of the spaces in the tensor product, obtained as prod(dims(P, s))`.

source
TensorKitSectors.dimMethod
dim(W::HomSpace)

Return the total dimension of a HomSpace, i.e. the number of linearly independent morphisms that can be constructed within this space.

source
TensorKit.dimsFunction
dims(::ProductSpace{S, N}) -> Dims{N} = NTuple{N, Int}

Return the dimensions of the spaces in the tensor product space as a tuple of integers.

source
dims(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}
+-> Dims{N} = NTuple{N, Int}

Return the degeneracy dimensions corresponding to a tuple of sectors s for each of the spaces in the tensor product P.

source
TensorKit.blocksectorsMethod
blocksectors(P::ProductSpace)

Return an iterator over the different unique coupled sector labels, i.e. the different fusion outputs that can be obtained by fusing the sectors present in the different spaces that make up the ProductSpace instance.

source
TensorKit.blocksectorsMethod
blocksectors(W::HomSpace)

Return an iterator over the different unique coupled sector labels, i.e. the intersection of the different fusion outputs that can be obtained by fusing the sectors present in the domain, as well as from the codomain.

See also hasblock.

source
TensorKit.hasblockFunction
hasblock(P::ProductSpace, c::Sector)

Query whether a coupled sector c appears with nonzero dimension in P, i.e. whether blockdim(P, c) > 0.

See also blockdim and blocksectors.

source
hasblock(W::HomSpace, c::Sector)

Query whether a coupled sector c appears in both the codomain and domain of W.

See also blocksectors.

source
hasblock(t::AbstractTensorMap, c::Sector) -> Bool

Verify whether a tensor has a block corresponding to a coupled sector c.

source
TensorKit.blockdimFunction
blockdim(P::ProductSpace, c::Sector)

Return the total dimension of a coupled sector c in the product space, by summing over all dim(P, s) for all tuples of sectors s::NTuple{N, <:Sector} that can fuse to c, counted with the correct multiplicity (i.e. number of ways in which s can fuse to c).

See also hasblock and blocksectors.

source
TensorKit.fusiontreesMethod
fusiontrees(P::ProductSpace, blocksector::Sector)

Return an iterator over all fusion trees that can be formed by fusing the sectors present in the different spaces that make up the ProductSpace instance into the coupled sector blocksector.

source
TensorKit.spaceFunction
space(a) -> VectorSpace

Return the vector space associated to object a.

source

The following methods act specifically on ElementarySpace spaces:

TensorKit.isdualFunction
isdual(V::ElementarySpace) -> Bool

Return wether an ElementarySpace V is normal or rather a dual space. Always returns false for spaces where V == dual(V).

source
TensorKitSectors.dualFunction
dual(V::VectorSpace) -> VectorSpace

Return the dual space of V; also obtained via V'. This should satisfy dual(dual(V)) == V. It is assumed that typeof(V) == typeof(V').

source
Base.conjFunction
conj(V::S) where {S<:ElementarySpace} -> S

Return the conjugate space of V. This should satisfy conj(conj(V)) == V.

For field(V)==ℝ, conj(V) == V. It is assumed that typeof(V) == typeof(conj(V)).

source
TensorKit.flipFunction
flip(V::S) where {S<:ElementarySpace} -> S

Return a single vector space of type S that has the same value of isdual as dual(V), but yet is isomorphic to V rather than to dual(V). The spaces flip(V) and dual(V) only differ in the case of GradedSpace{I}.

source
TensorKit.:⊕Function
⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S
+oplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the direct sum sum of the spaces V₁, V₂, ... Note that all the individual spaces should have the same value for isdual, as otherwise the direct sum is not defined.

source
Base.zeroMethod
zero(V::S) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces.

source
Base.oneunitFunction
oneunit(V::S) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type S that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from one(V::S), which returns the empty product space ProductSpace{S,0}(()).

source
TensorKit.supremumFunction
supremum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)

Return the supremum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≿ V₁, V ≿ V₂, ... and no other W ≺ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.

source
TensorKit.infimumFunction
infimum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)

Return the infimum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≾ V₁, V ≾ V₂, ... and no other W ≻ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.

source

while the following also work on both ElementarySpace and ProductSpace

Base.oneMethod
one(::S) where {S<:ElementarySpace} -> ProductSpace{S, 0}
+one(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S, 0}

Return a tensor product of zero spaces of type S, i.e. this is the unit object under the tensor product operation, such that V ⊗ one(V) == V.

source
TensorKit.fuseFunction
fuse(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S
+fuse(P::ProductSpace{S}) where {S<:ElementarySpace} -> S

Return a single vector space of type S that is isomorphic to the fusion product of the individual spaces V₁, V₂, ..., or the spaces contained in P.

source
TensorKitSectors.:⊗Method
⊗(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S

Create a ProductSpace{S}(V₁, V₂, V₃...) representing the tensor product of several elementary vector spaces. For convience, Julia's regular multiplication operator * applied to vector spaces has the same effect.

The tensor product structure is preserved, see fuse for returning a single elementary space of type S that is isomorphic to this tensor product.

source
TensorKitSectors.:⊠Method
⊠(V₁::VectorSpace, V₂::VectorSpace)

Given two vector spaces V₁ and V₂ (ElementarySpace or ProductSpace), or thus, objects of corresponding fusion categories $C₁$ and $C₂$, $V₁ ⊠ V₂$ constructs the Deligne tensor product, an object in $C₁ ⊠ C₂$ which is the natural tensor product of those categories. In particular, the corresponding type of sectors (simple objects) is given by sectortype(V₁ ⊠ V₂) == sectortype(V₁) ⊠ sectortype(V₂) and can be thought of as a tuple of the individual sectors.

The Deligne tensor product also works in the type domain and for sectors and tensors. For group representations, we have Rep[G₁] ⊠ Rep[G₂] == Rep[G₁ × G₂], i.e. these are the natural representation spaces of the direct product of two groups.

source
TensorKit.ismonomorphicFunction
ismonomorphic(V₁::VectorSpace, V₂::VectorSpace)
+V₁ ≾ V₂

Return whether there exist monomorphisms from V₁ to V₂, i.e. 'injective' morphisms with left inverses.

source
TensorKit.isepimorphicFunction
isepimorphic(V₁::VectorSpace, V₂::VectorSpace)
+V₁ ≿ V₂

Return whether there exist epimorphisms from V₁ to V₂, i.e. 'surjective' morphisms with right inverses.

source
TensorKit.isisomorphicFunction
isisomorphic(V₁::VectorSpace, V₂::VectorSpace)
+V₁ ≅ V₂

Return if V₁ and V₂ are isomorphic, meaning that there exists isomorphisms from V₁ to V₂, i.e. morphisms with left and right inverses.

source

There are also specific methods for HomSpace instances, that are used in determining the resuling HomSpace after applying certain tensor operations.

TensorKit.permuteMethod
permute(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})

Return the HomSpace obtained by permuting the indices of the domain and codomain of W according to the permutation p₁ and p₂ respectively.

source
TensorKit.selectMethod
select(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})

Return the HomSpace obtained by a selection from the domain and codomain of W according to the indices in p₁ and p₂ respectively.

source
TensorKit.composeMethod
compose(W::HomSpace, V::HomSpace)

Obtain the HomSpace that is obtained from composing the morphisms in W and V. For this to be possible, the domain of W must match the codomain of V.

source
TensorKit.insertleftunitMethod
insertleftunit(W::HomSpace, i::Int=numind(W) + 1; conj=false, dual=false)

Insert a trivial vector space, isomorphic to the underlying field, at position i. More specifically, adds a left monoidal unit or its dual.

See also insertrightunit, removeunit.

source
TensorKit.insertrightunitMethod
insertrightunit(W::HomSpace, i::Int=numind(W); conj=false, dual=false)

Insert a trivial vector space, isomorphic to the underlying field, after position i. More specifically, adds a right monoidal unit or its dual.

See also insertleftunit, removeunit.

source
diff --git a/previews/PR187/lib/tensors/index.html b/previews/PR187/lib/tensors/index.html index 5c124b17..4ef07c76 100644 --- a/previews/PR187/lib/tensors/index.html +++ b/previews/PR187/lib/tensors/index.html @@ -1,87 +1,89 @@ -Tensors · TensorKit.jl

Tensors

Type hierarchy

The abstract supertype of all tensors in TensorKit is given by AbstractTensorMap:

TensorKit.AbstractTensorMapType
abstract type AbstractTensorMap{T<:Number, S<:IndexSpace, N₁, N₂} end

Abstract supertype of all tensor maps, i.e. linear maps between tensor products of vector spaces of type S<:IndexSpace, with element type T. An AbstractTensorMap maps from an input space of type ProductSpace{S, N₂} to an output space of type ProductSpace{S, N₁}.

source

The following concrete subtypes are provided within the TensorKit library:

TensorKit.TensorMapType
struct TensorMap{T, S<:IndexSpace, N₁, N₂, A<:DenseVector{T}} <: AbstractTensorMap{T, S, N₁, N₂}

Specific subtype of AbstractTensorMap for representing tensor maps (morphisms in a tensor category), where the data is stored in a dense vector.

source
TensorKit.DiagonalTensorMapType
DiagonalTensorMap{T}(undef, domain::S) where {T,S<:IndexSpace}
+Tensors · TensorKit.jl

Tensors

Type hierarchy

The abstract supertype of all tensors in TensorKit is given by AbstractTensorMap:

TensorKit.AbstractTensorMapType
abstract type AbstractTensorMap{T<:Number, S<:IndexSpace, N₁, N₂} end

Abstract supertype of all tensor maps, i.e. linear maps between tensor products of vector spaces of type S<:IndexSpace, with element type T. An AbstractTensorMap maps from an input space of type ProductSpace{S, N₂} to an output space of type ProductSpace{S, N₁}.

source

The following concrete subtypes are provided within the TensorKit library:

TensorKit.TensorMapType
struct TensorMap{T, S<:IndexSpace, N₁, N₂, A<:DenseVector{T}} <: AbstractTensorMap{T, S, N₁, N₂}

Specific subtype of AbstractTensorMap for representing tensor maps (morphisms in a tensor category), where the data is stored in a dense vector.

source
TensorKit.DiagonalTensorMapType
DiagonalTensorMap{T}(undef, domain::S) where {T,S<:IndexSpace}
 # expert mode: select storage type `A`
-DiagonalTensorMap{T,S,A}(undef, domain::S) where {T,S<:IndexSpace,A<:DenseVector{T}}

Construct a DiagonalTensorMap with uninitialized data.

source
TensorKit.BraidingTensorType
struct BraidingTensor{T,S<:IndexSpace} <: AbstractTensorMap{T, S, 2, 2}
-BraidingTensor(V1::S, V2::S, adjoint::Bool=false) where {S<:IndexSpace}

Specific subtype of AbstractTensorMap for representing the braiding tensor that braids the first input over the second input; its inverse can be obtained as the adjoint.

It holds that domain(BraidingTensor(V1, V2)) == V1 ⊗ V2 and codomain(BraidingTensor(V1, V2)) == V2 ⊗ V1.

source

Of those, TensorMap provides the generic instantiation of our tensor concept. It supports various constructors, which are discussed in the next subsection.

Furthermore, some aliases are provided for convenience:

TensorKit.AbstractTensorType
AbstractTensor{T,S,N} = AbstractTensorMap{T,S,N,0}

Abstract supertype of all tensors, i.e. elements in the tensor product space of type ProductSpace{S, N}, with element type T.

An AbstractTensor{T, S, N} is actually a special case AbstractTensorMap{T, S, N, 0}, i.e. a tensor map with only non-trivial output spaces.

source
TensorKit.TensorType
Tensor{T, S, N, A<:DenseVector{T}} = TensorMap{T, S, N, 0, A}

Specific subtype of AbstractTensor for representing tensors whose data is stored in a dense vector.

A Tensor{T, S, N, A} is actually a special case TensorMap{T, S, N, 0, A}, i.e. a tensor map with only a non-trivial output space.

source

TensorMap constructors

General constructors

A TensorMap with undefined data can be constructed by specifying its domain and codomain:

TensorKit.TensorMapMethod
TensorMap{T}(undef, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂})
+DiagonalTensorMap{T,S,A}(undef, domain::S) where {T,S<:IndexSpace,A<:DenseVector{T}}

Construct a DiagonalTensorMap with uninitialized data.

source
TensorKit.BraidingTensorType
struct BraidingTensor{T,S<:IndexSpace} <: AbstractTensorMap{T, S, 2, 2}
+BraidingTensor(V1::S, V2::S, adjoint::Bool=false) where {S<:IndexSpace}

Specific subtype of AbstractTensorMap for representing the braiding tensor that braids the first input over the second input; its inverse can be obtained as the adjoint.

It holds that domain(BraidingTensor(V1, V2)) == V1 ⊗ V2 and codomain(BraidingTensor(V1, V2)) == V2 ⊗ V1.

source

Of those, TensorMap provides the generic instantiation of our tensor concept. It supports various constructors, which are discussed in the next subsection.

Furthermore, some aliases are provided for convenience:

TensorKit.AbstractTensorType
AbstractTensor{T,S,N} = AbstractTensorMap{T,S,N,0}

Abstract supertype of all tensors, i.e. elements in the tensor product space of type ProductSpace{S, N}, with element type T.

An AbstractTensor{T, S, N} is actually a special case AbstractTensorMap{T, S, N, 0}, i.e. a tensor map with only non-trivial output spaces.

source
TensorKit.TensorType
Tensor{T, S, N, A<:DenseVector{T}} = TensorMap{T, S, N, 0, A}

Specific subtype of AbstractTensor for representing tensors whose data is stored in a dense vector.

A Tensor{T, S, N, A} is actually a special case TensorMap{T, S, N, 0, A}, i.e. a tensor map with only a non-trivial output space.

source

TensorMap constructors

General constructors

A TensorMap with undefined data can be constructed by specifying its domain and codomain:

TensorKit.TensorMapMethod
TensorMap{T}(undef, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂})
             where {T,S,N₁,N₂}
 TensorMap{T}(undef, codomain ← domain)
 TensorMap{T}(undef, domain → codomain)
 # expert mode: select storage type `A`
 TensorMap{T,S,N₁,N₂,A}(undef, codomain ← domain)
-TensorMap{T,S,N₁,N₂,A}(undef, domain → domain)

Construct a TensorMap with uninitialized data.

source

The resulting object can then be filled with data using the setindex! method as discussed below, using functions such as VectorInterface.zerovector!, rand! or fill!, or it can be used as an output argument in one of the many methods that accept output arguments, or in an @tensor output[...] = ... expression.

Alternatively, a TensorMap can be constructed by specifying its data, codmain and domain in one of the following ways:

TensorKit.TensorMapMethod
TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, codomain::ProductSpace{S,N₁},
+TensorMap{T,S,N₁,N₂,A}(undef, domain → domain)

Construct a TensorMap with uninitialized data.

source

The resulting object can then be filled with data using the setindex! method as discussed below, using functions such as VectorInterface.zerovector!, rand! or fill!, or it can be used as an output argument in one of the many methods that accept output arguments, or in an @tensor output[...] = ... expression.

Alternatively, a TensorMap can be constructed by specifying its data, codmain and domain in one of the following ways:

TensorKit.TensorMapMethod
TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, codomain::ProductSpace{S,N₁},
             domain::ProductSpace{S,N₂}) where {S<:ElementarySpace,N₁,N₂}
 TensorMap(data, codomain ← domain)
-TensorMap(data, domain → codomain)

Construct a TensorMap by explicitly specifying its block data.

Arguments

  • data::AbstractDict{<:Sector,<:AbstractMatrix}: dictionary containing the block data for each coupled sector c as a matrix of size (blockdim(codomain, c), blockdim(domain, c)).
  • codomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.
  • domain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.

Alternatively, the domain and codomain can be specified by passing a HomSpace using the syntax codomain ← domain or domain → codomain.

source
TensorKit.TensorMapMethod
TensorMap(data::AbstractArray, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂};
+TensorMap(data, domain → codomain)

Construct a TensorMap by explicitly specifying its block data.

Arguments

  • data::AbstractDict{<:Sector,<:AbstractMatrix}: dictionary containing the block data for each coupled sector c as a matrix of size (blockdim(codomain, c), blockdim(domain, c)).
  • codomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.
  • domain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.

Alternatively, the domain and codomain can be specified by passing a HomSpace using the syntax codomain ← domain or domain → codomain.

source
TensorKit.TensorMapMethod
TensorMap(data::AbstractArray, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂};
                 tol=sqrt(eps(real(float(eltype(data)))))) where {S<:ElementarySpace,N₁,N₂}
 TensorMap(data, codomain ← domain; tol=sqrt(eps(real(float(eltype(data))))))
-TensorMap(data, domain → codomain; tol=sqrt(eps(real(float(eltype(data))))))

Construct a TensorMap from a plain multidimensional array.

Arguments

  • data::DenseArray: tensor data as a plain array.
  • codomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.
  • domain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.
  • tol=sqrt(eps(real(float(eltype(data)))))::Float64:

Here, data can be specified in three ways:

  1. data can be a DenseVector of length dim(codomain ← domain); in that case it represents the actual independent entries of the tensor map. An instance will be created that directly references data.
  2. data can be an AbstractMatrix of size (dim(codomain), dim(domain))
  3. data can be an AbstractArray of rank N₁ + N₂ with a size matching that of the domain and codomain spaces, i.e. size(data) == (dims(codomain)..., dims(domain)...)

In case 2 and 3, the TensorMap constructor will reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t), up to an error specified by tol. For the case where sectortype(S) == Trivial and data isa DenseArray, the data array is simply reshaped into a vector and used as in case 1 so that the memory will still be shared. In other cases, new memory will be allocated.

Note that in the case of N₁ + N₂ = 1, case 3 also amounts to data being a vector, whereas when N₁ + N₂ == 2, case 2 and case 3 both require data to be a matrix. Such ambiguous cases are resolved by checking the size of data in an attempt to support all possible cases.

Note

This constructor for case 2 and 3 only works for sectortype values for which conversion to a plain array is possible, and only in the case where the data actually respects the specified symmetry structure, up to a tolerance tol.

source

Finally, we also support the following Array-like constructors

Base.zerosMethod
zeros([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}
-zeros([T=Float64,], codomain ← domain)

Create a TensorMap with element type T, of all zeros with spaces specified by codomain and domain.

source
Base.onesMethod
ones([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}
-ones([T=Float64,], codomain ← domain)

Create a TensorMap with element type T, of all ones with spaces specified by codomain and domain.

source
Base.randMethod
rand([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
+TensorMap(data, domain → codomain; tol=sqrt(eps(real(float(eltype(data))))))

Construct a TensorMap from a plain multidimensional array.

Arguments

  • data::DenseArray: tensor data as a plain array.
  • codomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.
  • domain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.
  • tol=sqrt(eps(real(float(eltype(data)))))::Float64:

Here, data can be specified in three ways:

  1. data can be a DenseVector of length dim(codomain ← domain); in that case it represents the actual independent entries of the tensor map. An instance will be created that directly references data.
  2. data can be an AbstractMatrix of size (dim(codomain), dim(domain))
  3. data can be an AbstractArray of rank N₁ + N₂ with a size matching that of the domain and codomain spaces, i.e. size(data) == (dims(codomain)..., dims(domain)...)

In case 2 and 3, the TensorMap constructor will reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t), up to an error specified by tol. For the case where sectortype(S) == Trivial and data isa DenseArray, the data array is simply reshaped into a vector and used as in case 1 so that the memory will still be shared. In other cases, new memory will be allocated.

Note that in the case of N₁ + N₂ = 1, case 3 also amounts to data being a vector, whereas when N₁ + N₂ == 2, case 2 and case 3 both require data to be a matrix. Such ambiguous cases are resolved by checking the size of data in an attempt to support all possible cases.

Note

This constructor for case 2 and 3 only works for sectortype values for which conversion to a plain array is possible, and only in the case where the data actually respects the specified symmetry structure, up to a tolerance tol.

source

Finally, we also support the following Array-like constructors

Base.zerosMethod
zeros([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}
+zeros([T=Float64,], codomain ← domain)

Create a TensorMap with element type T, of all zeros with spaces specified by codomain and domain.

source
Base.onesMethod
ones([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}
+ones([T=Float64,], codomain ← domain)

Create a TensorMap with element type T, of all ones with spaces specified by codomain and domain.

source
Base.randMethod
rand([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
              domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t
-rand([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by rand.

See also (rand)!.

source
Base.randnMethod
randn([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
+rand([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by rand.

See also (rand)!.

source
Base.randnMethod
randn([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
              domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t
-randn([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by randn.

See also (randn)!.

source
Random.randexpMethod
randexp([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
+randn([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by randn.

See also (randn)!.

source
Random.randexpMethod
randexp([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},
              domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t
-randexp([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by randexp.

See also (randexp)!.

source

as well as a similar constructor

Base.similarMethod
similar(t::AbstractTensorMap, [AorT=storagetype(t)], [V=space(t)])
-similar(t::AbstractTensorMap, [AorT=storagetype(t)], codomain, domain)

Creates an uninitialized mutable tensor with the given scalar or storagetype AorT and structure V or codomain ← domain, based on the source tensormap. The second and third arguments are both optional, defaulting to the given tensor's storagetype and space. The structure may be specified either as a single HomSpace argument or as codomain and domain.

By default, this will result in TensorMap{T}(undef, V) when custom objects do not specialize this method.

source

Specific constructors

Additionally, the following methods can be used to construct specific TensorMap instances.

TensorKit.idFunction
id([T::Type=Float64,] V::TensorSpace) -> TensorMap

Construct the identity endomorphism on space V, i.e. return a t::TensorMap with domain(t) == codomain(t) == V, where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type.

source
TensorKit.isomorphismFunction
isomorphism([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
+randexp([rng=default_rng()], [T=Float64], codomain ← domain) -> t

Generate a tensor t with entries generated by randexp.

See also (randexp)!.

source

as well as a similar constructor

Base.similarMethod
similar(t::AbstractTensorMap, [AorT=storagetype(t)], [V=space(t)])
+similar(t::AbstractTensorMap, [AorT=storagetype(t)], codomain, domain)

Creates an uninitialized mutable tensor with the given scalar or storagetype AorT and structure V or codomain ← domain, based on the source tensormap. The second and third arguments are both optional, defaulting to the given tensor's storagetype and space. The structure may be specified either as a single HomSpace argument or as codomain and domain.

By default, this will result in TensorMap{T}(undef, V) when custom objects do not specialize this method.

source

Specific constructors

Additionally, the following methods can be used to construct specific TensorMap instances.

TensorKit.idFunction
id([T::Type=Float64,] V::TensorSpace) -> TensorMap

Construct the identity endomorphism on space V, i.e. return a t::TensorMap with domain(t) == codomain(t) == V, where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type.

source
TensorKit.isomorphismFunction
isomorphism([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
 isomorphism([T::Type=Float64,] codomain ← domain) -> TensorMap
-isomorphism([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific isomorphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, an error will be thrown.

Note

There is no canonical choice for a specific isomorphism, but the current choice is such that isomorphism(cod, dom) == inv(isomorphism(dom, cod)).

See also unitary when InnerProductStyle(cod) === EuclideanInnerProduct().

source
TensorKit.unitaryFunction
unitary([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
+isomorphism([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific isomorphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, an error will be thrown.

Note

There is no canonical choice for a specific isomorphism, but the current choice is such that isomorphism(cod, dom) == inv(isomorphism(dom, cod)).

See also unitary when InnerProductStyle(cod) === EuclideanInnerProduct().

source
TensorKit.unitaryFunction
unitary([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
 unitary([T::Type=Float64,] codomain ← domain) -> TensorMap
-unitary([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific unitary morphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, or the spacetype does not have a Euclidean inner product, an error will be thrown.

Note

There is no canonical choice for a specific unitary, but the current choice is such that unitary(cod, dom) == inv(unitary(dom, cod)) = adjoint(unitary(dom, cod)).

See also isomorphism and isometry.

source
TensorKit.isometryFunction
isometry([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
+unitary([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific unitary morphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, or the spacetype does not have a Euclidean inner product, an error will be thrown.

Note

There is no canonical choice for a specific unitary, but the current choice is such that unitary(cod, dom) == inv(unitary(dom, cod)) = adjoint(unitary(dom, cod)).

See also isomorphism and isometry.

source
TensorKit.isometryFunction
isometry([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap
 isometry([T::Type=Float64,] codomain ← domain) -> TensorMap
-isometry([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific isometry between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. The isometry t then satisfies t' * t = id(domain) and (t * t')^2 = t * t'. If the spaces do not allow for such an isometric inclusion, an error will be thrown.

See also isomorphism and unitary.

source

AbstractTensorMap properties and data access

The following methods exist to obtain type information:

Base.eltypeMethod
eltype(::AbstractTensorMap) -> Type{T}
-eltype(::Type{<:AbstractTensorMap}) -> Type{T}

Return the scalar or element type T of a tensor.

source
TensorKit.spacetypeMethod
spacetype(::AbstractTensorMap) -> Type{S<:IndexSpace}
-spacetype(::Type{<:AbstractTensorMap}) -> Type{S<:IndexSpace}

Return the type of the elementary space S of a tensor.

source
TensorKit.sectortypeMethod
sectortype(::AbstractTensorMap) -> Type{I<:Sector}
-sectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}

Return the type of sector I of a tensor.

source
TensorKit.fieldMethod
field(::AbstractTensorMap) -> Type{𝔽<:Field}
-field(::Type{<:AbstractTensorMap}) -> Type{𝔽<:Field}

Return the type of field 𝔽 of a tensor.

source
TensorKit.storagetypeFunction
storagetype(t::AbstractTensorMap) -> Type{A<:AbstractVector}
-storagetype(T::Type{<:AbstractTensorMap}) -> Type{A<:AbstractVector}

Return the type of vector that stores the data of a tensor.

source

To obtain information about the indices, you can use:

TensorKit.spaceMethod
space(t::AbstractTensorMap{T,S,N₁,N₂}) -> HomSpace{S,N₁,N₂}
-space(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

The index information of a tensor, i.e. the HomSpace of its domain and codomain. If i is specified, return the i-th index space.

source
TensorKit.domainFunction
domain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₂}
-domain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

Return the domain of a tensor, i.e. the product space of the input spaces. If i is specified, return the i-th input space. Implementations should provide domain(t).

See also codomain and space.

source
TensorKit.codomainFunction
codomain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₁}
-codomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

Return the codomain of a tensor, i.e. the product space of the output spaces. If i is specified, return the i-th output space. Implementations should provide codomain(t).

See also domain and space.

source
TensorKit.numinFunction
numin(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int

Return the number of input spaces of a tensor. This is equivalent to the number of spaces in the domain of that tensor.

See also numout and numind.

source
TensorKit.numoutFunction
numout(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int

Return the number of output spaces of a tensor. This is equivalent to the number of spaces in the codomain of that tensor.

See also numin and numind.

source
TensorKit.numindFunction
numind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Int

Return the total number of input and output spaces of a tensor. This is equivalent to the total number of spaces in the domain and codomain of that tensor.

See also numout and numin.

source
TensorKit.allindFunction
allind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}

Return all indices of a tensor, i.e. the indices of its domain and codomain.

See also codomainind and domainind.

source

In TensorMap instances, all data is gathered in a single AbstractVector, which has an internal structure into blocks associated to total coupled charge, within which live subblocks associated with the different possible fusion-splitting tree pairs.

To obtain information about the structure of the data, you can use:

TensorKit.fusionblockstructureMethod
fusionblockstructure(t::AbstractTensorMap) -> TensorStructure

Return the necessary structure information to decompose a tensor in blocks labeled by coupled sectors and in subblocks labeled by a splitting-fusion tree couple.

source
TensorKitSectors.dimMethod
dim(t::AbstractTensorMap) -> Int

The total number of free parameters of a tensor, discounting the entries that are fixed by symmetry. This is also the dimension of the HomSpace on which the TensorMap is defined.

source
TensorKit.hasblockMethod
hasblock(t::AbstractTensorMap, c::Sector) -> Bool

Verify whether a tensor has a block corresponding to a coupled sector c.

source
TensorKit.fusiontreesMethod
fusiontrees(t::AbstractTensorMap)

Return an iterator over all splitting - fusion tree pairs of a tensor.

source

Data can be accessed (and modified) in a number of ways. To access the full matrix block associated with the coupled charges, you can use:

To access the data associated with a specific fusion tree pair, you can use:

Base.getindexMethod
Base.getindex(t::TensorMap{T,S,N₁,N₂,I},
+isometry([T::Type=Float64,] domain → codomain) -> TensorMap

Construct a specific isometry between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. The isometry t then satisfies t' * t = id(domain) and (t * t')^2 = t * t'. If the spaces do not allow for such an isometric inclusion, an error will be thrown.

See also isomorphism and unitary.

source

AbstractTensorMap properties and data access

The following methods exist to obtain type information:

Base.eltypeMethod
eltype(::AbstractTensorMap) -> Type{T}
+eltype(::Type{<:AbstractTensorMap}) -> Type{T}

Return the scalar or element type T of a tensor.

source
TensorKit.spacetypeMethod
spacetype(::AbstractTensorMap) -> Type{S<:IndexSpace}
+spacetype(::Type{<:AbstractTensorMap}) -> Type{S<:IndexSpace}

Return the type of the elementary space S of a tensor.

source
TensorKit.sectortypeMethod
sectortype(::AbstractTensorMap) -> Type{I<:Sector}
+sectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}

Return the type of sector I of a tensor.

source
TensorKit.fieldMethod
field(::AbstractTensorMap) -> Type{𝔽<:Field}
+field(::Type{<:AbstractTensorMap}) -> Type{𝔽<:Field}

Return the type of field 𝔽 of a tensor.

source
TensorKit.storagetypeFunction
storagetype(t::AbstractTensorMap) -> Type{A<:AbstractVector}
+storagetype(T::Type{<:AbstractTensorMap}) -> Type{A<:AbstractVector}

Return the type of vector that stores the data of a tensor.

source

To obtain information about the indices, you can use:

TensorKit.spaceMethod
space(t::AbstractTensorMap{T,S,N₁,N₂}) -> HomSpace{S,N₁,N₂}
+space(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

The index information of a tensor, i.e. the HomSpace of its domain and codomain. If i is specified, return the i-th index space.

source
TensorKit.domainFunction
domain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₂}
+domain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

Return the domain of a tensor, i.e. the product space of the input spaces. If i is specified, return the i-th input space. Implementations should provide domain(t).

See also codomain and space.

source
TensorKit.codomainFunction
codomain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₁}
+codomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S

Return the codomain of a tensor, i.e. the product space of the output spaces. If i is specified, return the i-th output space. Implementations should provide codomain(t).

See also domain and space.

source
TensorKit.numinFunction
numin(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int

Return the number of input spaces of a tensor. This is equivalent to the number of spaces in the domain of that tensor.

See also numout and numind.

source
TensorKit.numoutFunction
numout(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int

Return the number of output spaces of a tensor. This is equivalent to the number of spaces in the codomain of that tensor.

See also numin and numind.

source
TensorKit.numindFunction
numind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Int

Return the total number of input and output spaces of a tensor. This is equivalent to the total number of spaces in the domain and codomain of that tensor.

See also numout and numin.

source
TensorKit.allindFunction
allind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}

Return all indices of a tensor, i.e. the indices of its domain and codomain.

See also codomainind and domainind.

source

In TensorMap instances, all data is gathered in a single AbstractVector, which has an internal structure into blocks associated to total coupled charge, within which live subblocks associated with the different possible fusion-splitting tree pairs.

To obtain information about the structure of the data, you can use:

TensorKit.fusionblockstructureMethod
fusionblockstructure(t::AbstractTensorMap) -> TensorStructure

Return the necessary structure information to decompose a tensor in blocks labeled by coupled sectors and in subblocks labeled by a splitting-fusion tree couple.

source
TensorKitSectors.dimMethod
dim(t::AbstractTensorMap) -> Int

The total number of free parameters of a tensor, discounting the entries that are fixed by symmetry. This is also the dimension of the HomSpace on which the TensorMap is defined.

source
TensorKit.hasblockMethod
hasblock(t::AbstractTensorMap, c::Sector) -> Bool

Verify whether a tensor has a block corresponding to a coupled sector c.

source
TensorKit.fusiontreesMethod
fusiontrees(t::AbstractTensorMap)

Return an iterator over all splitting - fusion tree pairs of a tensor.

source

Data can be accessed (and modified) in a number of ways. To access the full matrix block associated with the coupled charges, you can use:

To access the data associated with a specific fusion tree pair, you can use:

Base.getindexMethod
Base.getindex(t::TensorMap{T,S,N₁,N₂,I},
               f₁::FusionTree{I,N₁},
               f₂::FusionTree{I,N₂}) where {T,SN₁,N₂,I<:Sector}
     -> StridedViews.StridedView
-t[f₁, f₂]

Return a view into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). In particular, if f₁.coupled == f₂.coupled == c, then a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) is returned which represents the slice of block(t, c) whose row indices correspond to f₁.uncoupled and column indices correspond to f₂.uncoupled.

source
Base.setindex!Method
Base.setindex!(t::TensorMap{T,S,N₁,N₂,I},
+t[f₁, f₂]

Return a view into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). In particular, if f₁.coupled == f₂.coupled == c, then a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) is returned which represents the slice of block(t, c) whose row indices correspond to f₁.uncoupled and column indices correspond to f₂.uncoupled.

source
Base.setindex!Method
Base.setindex!(t::TensorMap{T,S,N₁,N₂,I},
                v,
                f₁::FusionTree{I,N₁},
                f₂::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}
-t[f₁, f₂] = v

Copies v into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). Here, v can be any object that can be copied into a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) using Base.copy!.

See also Base.getindex(::TensorMap{T,S,N₁,N₂,I<:Sector}, ::FusionTree{I<:Sector,N₁}, ::FusionTree{I<:Sector,N₂})

source

For a tensor t with FusionType(sectortype(t)) isa UniqeFuison, fusion trees are completely determined by the outcoming sectors, and the data can be accessed in a more straightforward way:

For a tensor t with FusionType(sectortype(t)) isa UniqeFuison, fusion trees are completely determined by the outcoming sectors, and the data can be accessed in a more straightforward way:

Base.getindexMethod
Base.getindex(t::TensorMap
               sectors::NTuple{N₁+N₂,I}) where {N₁,N₂,I<:Sector} 
     -> StridedViews.StridedView
-t[sectors]

Return a view into the data slice of t corresponding to the splitting - fusion tree pair with combined uncoupled charges sectors. In particular, if sectors == (s₁..., s₂...) where s₁ and s₂ correspond to the coupled charges in the codomain and domain respectively, then a StridedViews.StridedView of size (dims(codomain(t), s₁)..., dims(domain(t), s₂)) is returned.

This method is only available for the case where FusionStyle(I) isa UniqueFusion, since it assumes a uniquely defined coupled charge.

source

For tensor t with sectortype(t) == Trivial, the data can be accessed and manipulated directly as multidimensional arrays:

Base.getindexMethod
Base.getindex(t::AbstractTensorMap)
-t[]

Return a view into the data of t as a StridedViews.StridedView of size (dims(codomain(t))..., dims(domain(t))...).

source
Base.getindexMethod
Base.getindex(t::AbstractTensorMap, indices::Vararg{Int})
-t[indices]

Return a view into the data slice of t corresponding to indices, by slicing the StridedViews.StridedView into the full data array.

source
Base.setindex!Method
Base.setindex!(t::AbstractTensorMap, v, indices::Vararg{Int})
-t[indices] = v

Assigns v to the data slice of t corresponding to indices.

source

AbstractTensorMap operations

The operations that can be performed on an AbstractTensorMap can be organized into the following categories:

  • vector operations: these do not change the space or index strucure of a tensor and can be straightforwardly implemented on on the full data. All the methods described in VectorInterface.jl are supported. For compatibility reasons, we also provide implementations for equivalent methods from LinearAlgebra.jl, such as axpy!, axpby!.

  • index manipulations: these change (permute) the index structure of a tensor, which affects the data in a way that is fully determined by the categorical data of the sectortype of the tensor.

  • (planar) contractions and (planar) traces (i.e., contractions with identity tensors). Tensor contractions correspond to a combination of some index manipulations followed by a composition or multiplication of the tensors in their role as linear maps. Tensor contractions are however of such important and frequency that they require a dedicated implementation.

  • tensor factorisations, which relies on their identification of tensors with linear maps between tensor spaces. The factorisations are applied as ordinary matrix factorisations to the matrix blocks associated with the coupled charges.

Index manipulations

A general index manipulation of a TensorMap object can be built up by considering some transformation of the fusion trees, along with a permutation of the stored data. They come in three flavours, which are either of the type transform(!) which are exported, or of the type add_transform!, for additional expert-mode options that allows for addition and scaling, as well as the selection of a custom backend.

TensorKit.permuteMethod
permute(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;
+t[sectors]

Return a view into the data slice of t corresponding to the splitting - fusion tree pair with combined uncoupled charges sectors. In particular, if sectors == (s₁..., s₂...) where s₁ and s₂ correspond to the coupled charges in the codomain and domain respectively, then a StridedViews.StridedView of size (dims(codomain(t), s₁)..., dims(domain(t), s₂)) is returned.

This method is only available for the case where FusionStyle(I) isa UniqueFusion, since it assumes a uniquely defined coupled charge.

source

For tensor t with sectortype(t) == Trivial, the data can be accessed and manipulated directly as multidimensional arrays:

Base.getindexMethod
Base.getindex(t::AbstractTensorMap)
+t[]

Return a view into the data of t as a StridedViews.StridedView of size (dims(codomain(t))..., dims(domain(t))...).

source
Base.getindexMethod
Base.getindex(t::AbstractTensorMap, indices::Vararg{Int})
+t[indices]

Return a view into the data slice of t corresponding to indices, by slicing the StridedViews.StridedView into the full data array.

source
Base.setindex!Method
Base.setindex!(t::AbstractTensorMap, v, indices::Vararg{Int})
+t[indices] = v

Assigns v to the data slice of t corresponding to indices.

source

AbstractTensorMap operations

The operations that can be performed on an AbstractTensorMap can be organized into the following categories:

  • vector operations: these do not change the space or index strucure of a tensor and can be straightforwardly implemented on on the full data. All the methods described in VectorInterface.jl are supported. For compatibility reasons, we also provide implementations for equivalent methods from LinearAlgebra.jl, such as axpy!, axpby!.

  • index manipulations: these change (permute) the index structure of a tensor, which affects the data in a way that is fully determined by the categorical data of the sectortype of the tensor.

  • (planar) contractions and (planar) traces (i.e., contractions with identity tensors). Tensor contractions correspond to a combination of some index manipulations followed by a composition or multiplication of the tensors in their role as linear maps. Tensor contractions are however of such important and frequency that they require a dedicated implementation.

  • tensor factorisations, which relies on their identification of tensors with linear maps between tensor spaces. The factorisations are applied as ordinary matrix factorisations to the matrix blocks associated with the coupled charges.

Index manipulations

A general index manipulation of a TensorMap object can be built up by considering some transformation of the fusion trees, along with a permutation of the stored data. They come in three flavours, which are either of the type transform(!) which are exported, or of the type add_transform!, for additional expert-mode options that allows for addition and scaling, as well as the selection of a custom backend.

TensorKit.permuteMethod
permute(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;
         copy::Bool=false)
-    -> tdst::TensorMap

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

To permute into an existing destination, see permute! and add_permute!

source
TensorKit.braidMethod
braid(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple, levels::IndexTuple;
+    -> tdst::TensorMap

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

To permute into an existing destination, see permute! and add_permute!

source
TensorKit.braidMethod
braid(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple, levels::IndexTuple;
       copy::Bool = false)
-    -> tdst::TensorMap

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

To braid into an existing destination, see braid! and add_braid!

source
Base.transposeMethod
transpose(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;
+    -> tdst::TensorMap

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

To braid into an existing destination, see braid! and add_braid!

source
Base.transposeMethod
transpose(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;
           copy::Bool=false)
-    -> tdst::TensorMap

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

To permute into an existing destination, see permute! and add_permute!

source
TensorKit.repartitionMethod
repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}
-    -> tdst::AbstractTensorMap{S,N₁,N₂}

Return tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

To repartition into an existing destination, see repartition!.

source
TensorKitSectors.twistMethod
twist(tsrc::AbstractTensorMap, i::Int; inv::Bool=false) -> tdst
-twist(tsrc::AbstractTensorMap, is; inv::Bool=false) -> tdst

Apply a twist to the ith index of tsrc and return the result as a new tensor. If inv=true, use the inverse twist.

See twist! for storing the result in place.

source
Base.permute!Method
permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple)
-    -> tdst

Write into tdst the result of permuting the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively.

See permute for creating a new tensor and add_permute! for a more general version.

source
TensorKit.braid!Function
braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
+    -> tdst::TensorMap

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

To permute into an existing destination, see permute! and add_permute!

source
TensorKit.repartitionMethod
repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}
+    -> tdst::AbstractTensorMap{S,N₁,N₂}

Return tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

To repartition into an existing destination, see repartition!.

source
TensorKitSectors.twistMethod
twist(tsrc::AbstractTensorMap, i::Int; inv::Bool=false) -> tdst
+twist(tsrc::AbstractTensorMap, is; inv::Bool=false) -> tdst

Apply a twist to the ith index of tsrc and return the result as a new tensor. If inv=true, use the inverse twist.

See twist! for storing the result in place.

source
TensorKit.insertleftunitMethod
insertleftunit(tsrc::AbstractTensorMap, i::Int=numind(t) + 1;
+               conj=false, dual=false, copy=false) -> tdst

Insert a trivial vector space, isomorphic to the underlying field, at position i. More specifically, adds a left monoidal unit or its dual.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

See also insertrightunit and removeunit.

source
TensorKit.insertrightunitMethod
insertrightunit(tsrc::AbstractTensorMap, i::Int=numind(t);
+                conj=false, dual=false, copy=false) -> tdst

Insert a trivial vector space, isomorphic to the underlying field, after position i. More specifically, adds a right monoidal unit or its dual.

If copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.

See also insertleftunit and removeunit.

source
Base.permute!Method
permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple)
+    -> tdst

Write into tdst the result of permuting the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively.

See permute for creating a new tensor and add_permute! for a more general version.

source
TensorKit.braid!Function
braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
        (p₁, p₂)::Index2Tuple, levels::Tuple)
-    -> tdst

Write into tdst the result of 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.

See braid for creating a new tensor and add_braid! for a more general version.

source
LinearAlgebra.transpose!Function
transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
+    -> tdst

Write into tdst the result of 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.

See braid for creating a new tensor and add_braid! for a more general version.

source
LinearAlgebra.transpose!Function
transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
            (p₁, p₂)::Index2Tuple)
-    -> tdst

Write into tdst the result of 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))...).

See transpose for creating a new tensor and add_transpose! for a more general version.

source
TensorKit.repartition!Function
repartition!(tdst::AbstractTensorMap{S}, tsrc::AbstractTensorMap{S}) where {S} -> tdst

Write into tdst the result of repartitioning the indices of tsrc. This is just a special case of a transposition that only changes the number of in- and outgoing indices.

See repartition for creating a new tensor.

source
TensorKit.twist!Function
twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) -> t
-twist!(t::AbstractTensorMap, is; inv::Bool=false) -> t

Apply a twist to the ith index of t, or all indices in is, storing the result in t. If inv=true, use the inverse twist.

See twist for creating a new tensor.

source
TensorKit.add_permute!Function
add_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
-             α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂).

See also permute, permute!, add_braid!, add_transpose!.

source
TensorKit.add_braid!Function
add_braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
-           levels::IndexTuple, α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after braiding the indices of tsrc according to (p₁, p₂) and levels.

See also braid, braid!, add_permute!, add_transpose!.

source
TensorKit.add_transpose!Function
add_transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
-               α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after transposing the indices of tsrc according to (p₁, p₂).

See also transpose, transpose!, add_permute!, add_braid!.

source

Tensor map composition, traces, contractions and tensor products

TensorKit.composeMethod
compose(t1::AbstractTensorMap, t2::AbstractTensorMap) -> AbstractTensorMap

Return the AbstractTensorMap that implements the composition of the two tensor maps t1 and t2.

source
TensorKit.trace_permute!Function
trace_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
+    -> tdst

Write into tdst the result of 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))...).

See transpose for creating a new tensor and add_transpose! for a more general version.

source
TensorKit.repartition!Function
repartition!(tdst::AbstractTensorMap{S}, tsrc::AbstractTensorMap{S}) where {S} -> tdst

Write into tdst the result of repartitioning the indices of tsrc. This is just a special case of a transposition that only changes the number of in- and outgoing indices.

See repartition for creating a new tensor.

source
TensorKit.twist!Function
twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) -> t
+twist!(t::AbstractTensorMap, is; inv::Bool=false) -> t

Apply a twist to the ith index of t, or all indices in is, storing the result in t. If inv=true, use the inverse twist.

See twist for creating a new tensor.

source
TensorKit.add_permute!Function
add_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
+             α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂).

See also permute, permute!, add_braid!, add_transpose!.

source
TensorKit.add_braid!Function
add_braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
+           levels::IndexTuple, α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after braiding the indices of tsrc according to (p₁, p₂) and levels.

See also braid, braid!, add_permute!, add_transpose!.

source
TensorKit.add_transpose!Function
add_transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,
+               α::Number, β::Number, backend::AbstractBackend...)

Return the updated tdst, which is the result of adding α * tsrc to tdst after transposing the indices of tsrc according to (p₁, p₂).

See also transpose, transpose!, add_permute!, add_braid!.

source

Tensor map composition, traces, contractions and tensor products

TensorKit.composeMethod
compose(t1::AbstractTensorMap, t2::AbstractTensorMap) -> AbstractTensorMap

Return the AbstractTensorMap that implements the composition of the two tensor maps t1 and t2.

source
TensorKit.trace_permute!Function
trace_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,
                (p₁, p₂)::Index2Tuple, (q₁, q₂)::Index2Tuple,
-               α::Number, β::Number, backend=TO.DefaultBackend())

Return the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂) and furthermore tracing the indices in q₁ and q₂.

source
TensorKit.contract!Function
contract!(C::AbstractTensorMap,
+               α::Number, β::Number, backend=TO.DefaultBackend())

Return the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂) and furthermore tracing the indices in q₁ and q₂.

source
TensorKit.contract!Function
contract!(C::AbstractTensorMap,
           A::AbstractTensorMap, (oindA, cindA)::Index2Tuple,
           B::AbstractTensorMap, (cindB, oindB)::Index2Tuple,
           (p₁, p₂)::Index2Tuple,
           α::Number, β::Number,
-          backend, allocator)

Return the updated C, which is the result of adding α * A * B to C after permuting the indices of A and B according to (oindA, cindA) and (cindB, oindB) respectively.

source
TensorKitSectors.:⊗Method
⊗(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap
-otimes(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap

Compute the tensor product between two AbstractTensorMap instances, which results in a new TensorMap instance whose codomain is codomain(t1) ⊗ codomain(t2) and whose domain is domain(t1) ⊗ domain(t2).

source

TensorMap factorizations

The factorisation methods come in two flavors, namely a non-destructive version where you can specify an additional permutation of the domain and codomain indices before the factorisation is performed (provided that sectorstyle(t) has a symmetric braiding) as well as a destructive version The non-destructive methods are given first:

TensorKit.leftorthFunction
leftorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
-            alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R

Create orthonormal basis Q for indices in leftind, and remainder R such that permute(t, (leftind, rightind)) = Q*R.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftorth!(t, alg = QRpos()).

Different algorithms are available, namely QR(), QRpos(), SVD() and Polar(). QR() and QRpos() use a standard QR decomposition, producing an upper triangular matrix R. Polar() produces a Hermitian and positive semidefinite R. QRpos() corrects the standard QR decomposition such that the diagonal elements of R are positive. Only QRpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.rightorthFunction
rightorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
-            alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q

Create orthonormal basis Q for indices in rightind, and remainder L such that permute(t, (leftind, rightind)) = L*Q.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightorth!(t, alg = LQpos()).

Different algorithms are available, namely LQ(), LQpos(), RQ(), RQpos(), SVD() and Polar(). LQ() and LQpos() produce a lower triangular matrix L and are computed using a QR decomposition of the transpose. RQ() and RQpos() produce an upper triangular remainder L and only works if the total left dimension is smaller than or equal to the total right dimension. LQpos() and RQpos() add an additional correction such that the diagonal elements of L are positive. Polar() produces a Hermitian and positive semidefinite L. Only LQpos(), RQpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.leftnullFunction
leftnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;
-            alg::OrthogonalFactorizationAlgorithm = QRpos()) -> N

Create orthonormal basis for the orthogonal complement of the support of the indices in leftind, such that N' * permute(t, (leftind, rightind)) = 0.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftnull!(t, alg = QRpos()).

Different algorithms are available, namely QR() (or equivalently, QRpos()), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.rightnullFunction
rightnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;
+          backend, allocator)

Return the updated C, which is the result of adding α * A * B to C after permuting the indices of A and B according to (oindA, cindA) and (cindB, oindB) respectively.

source
TensorKitSectors.:⊗Method
⊗(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap
+otimes(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap

Compute the tensor product between two AbstractTensorMap instances, which results in a new TensorMap instance whose codomain is codomain(t1) ⊗ codomain(t2) and whose domain is domain(t1) ⊗ domain(t2).

source

TensorMap factorizations

The factorisation methods come in two flavors, namely a non-destructive version where you can specify an additional permutation of the domain and codomain indices before the factorisation is performed (provided that sectorstyle(t) has a symmetric braiding) as well as a destructive version The non-destructive methods are given first:

TensorKit.leftorthFunction
leftorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
+            alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R

Create orthonormal basis Q for indices in leftind, and remainder R such that permute(t, (leftind, rightind)) = Q*R.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftorth!(t, alg = QRpos()).

Different algorithms are available, namely QR(), QRpos(), SVD() and Polar(). QR() and QRpos() use a standard QR decomposition, producing an upper triangular matrix R. Polar() produces a Hermitian and positive semidefinite R. QRpos() corrects the standard QR decomposition such that the diagonal elements of R are positive. Only QRpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.rightorthFunction
rightorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
+            alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q

Create orthonormal basis Q for indices in rightind, and remainder L such that permute(t, (leftind, rightind)) = L*Q.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightorth!(t, alg = LQpos()).

Different algorithms are available, namely LQ(), LQpos(), RQ(), RQpos(), SVD() and Polar(). LQ() and LQpos() produce a lower triangular matrix L and are computed using a QR decomposition of the transpose. RQ() and RQpos() produce an upper triangular remainder L and only works if the total left dimension is smaller than or equal to the total right dimension. LQpos() and RQpos() add an additional correction such that the diagonal elements of L are positive. Polar() produces a Hermitian and positive semidefinite L. Only LQpos(), RQpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.leftnullFunction
leftnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;
+            alg::OrthogonalFactorizationAlgorithm = QRpos()) -> N

Create orthonormal basis for the orthogonal complement of the support of the indices in leftind, such that N' * permute(t, (leftind, rightind)) = 0.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftnull!(t, alg = QRpos()).

Different algorithms are available, namely QR() (or equivalently, QRpos()), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.rightnullFunction
rightnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;
             alg::OrthogonalFactorizationAlgorithm = LQ(),
             atol::Real = 0.0,
-            rtol::Real = eps(real(float(one(scalartype(t)))))*iszero(atol)) -> N

Create orthonormal basis for the orthogonal complement of the support of the indices in rightind, such that permute(t, (leftind, rightind))*N' = 0.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightnull!(t, alg = LQpos()).

Different algorithms are available, namely LQ() (or equivalently, LQpos), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.tsvdFunction
tsvd(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
+            rtol::Real = eps(real(float(one(scalartype(t)))))*iszero(atol)) -> N

Create orthonormal basis for the orthogonal complement of the support of the indices in rightind, such that permute(t, (leftind, rightind))*N' = 0.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightnull!(t, alg = LQpos()).

Different algorithms are available, namely LQ() (or equivalently, LQpos), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.tsvdFunction
tsvd(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;
     trunc::TruncationScheme = notrunc(), p::Real = 2, alg::Union{SVD, SDD} = SDD())
-    -> U, S, V, ϵ

Compute the (possibly truncated) singular value decomposition such that norm(permute(t, (leftind, rightind)) - U * S * V) ≈ ϵ, where ϵ thus represents the truncation error.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using tsvd!(t, trunc = notrunc(), p = 2).

A truncation parameter trunc can be specified for the new internal dimension, in which case a truncated singular value decomposition will be computed. Choices are:

  • notrunc(): no truncation (default);
  • truncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η;
  • truncdim(χ::Int): truncates such that the equivalent total dimension of the internal vector space is no larger than χ;
  • truncspace(V): truncates such that the dimension of the internal vector space is smaller than that of V in any sector.
  • truncbelow(η::Real): truncates such that every singular value is larger then η ;

Truncation options can also be combined using &, i.e. truncbelow(η) & truncdim(χ) will choose the truncation space such that every singular value is larger than η, and the equivalent total dimension of the internal vector space is no larger than χ.

The method tsvd also returns the truncation error ϵ, computed as the p norm of the singular values that were truncated.

THe keyword alg can be equal to SVD() or SDD(), corresponding to the underlying LAPACK algorithm that computes the decomposition (_gesvd or _gesdd).

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and tsvd(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.eighFunction
eigh(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eigh assumes that the linear map is hermitian and D and V tensors with the same scalartype as t. See eig and eigen for non-hermitian tensors. Hermiticity requires that the tensor acts on inner product spaces, and the current implementation requires InnerProductStyle(t) === EuclideanInnerProduct().

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigh!(t). Note that the permuted tensor on which eigh! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

See also eigen and eig.

source
TensorKit.eigFunction
eig(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eig assumes that the linear map is not hermitian and returns type stable complex valued D and V tensors for both real and complex valued t. See eigh for hermitian linear maps

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eig!(t). Note that the permuted tensor on which eig! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

Accepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.

See also eigen and eigh.

source
LinearAlgebra.eigenFunction
eigen(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigen!(t). Note that the permuted tensor on which eigen! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

Accepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.

See also eig and eigh

source
LinearAlgebra.isposdefFunction
isposdef(t::AbstractTensor, (leftind, rightind)::Index2Tuple) -> ::Bool

Test whether a tensor t is positive definite as linear map from rightind to leftind.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using isposdef!(t). Note that the permuted tensor on which isposdef! is called should have equal domain and codomain, as otherwise it is meaningless.

source

The corresponding destructive methods have an exclamation mark at the end of their name, and only accept the TensorMap object as well as the method-specific algorithm and keyword arguments.

TODO: document svd truncation types

+ -> U, S, V, ϵ

Compute the (possibly truncated) singular value decomposition such that norm(permute(t, (leftind, rightind)) - U * S * V) ≈ ϵ, where ϵ thus represents the truncation error.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using tsvd!(t, trunc = notrunc(), p = 2).

A truncation parameter trunc can be specified for the new internal dimension, in which case a truncated singular value decomposition will be computed. Choices are:

  • notrunc(): no truncation (default);
  • truncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η;
  • truncdim(χ::Int): truncates such that the equivalent total dimension of the internal vector space is no larger than χ;
  • truncspace(V): truncates such that the dimension of the internal vector space is smaller than that of V in any sector.
  • truncbelow(η::Real): truncates such that every singular value is larger then η ;

Truncation options can also be combined using &, i.e. truncbelow(η) & truncdim(χ) will choose the truncation space such that every singular value is larger than η, and the equivalent total dimension of the internal vector space is no larger than χ.

The method tsvd also returns the truncation error ϵ, computed as the p norm of the singular values that were truncated.

THe keyword alg can be equal to SVD() or SDD(), corresponding to the underlying LAPACK algorithm that computes the decomposition (_gesvd or _gesdd).

Orthogonality requires InnerProductStyle(t) <: HasInnerProduct, and tsvd(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().

source
TensorKit.eighFunction
eigh(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eigh assumes that the linear map is hermitian and D and V tensors with the same scalartype as t. See eig and eigen for non-hermitian tensors. Hermiticity requires that the tensor acts on inner product spaces, and the current implementation requires InnerProductStyle(t) === EuclideanInnerProduct().

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigh!(t). Note that the permuted tensor on which eigh! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

See also eigen and eig.

source
TensorKit.eigFunction
eig(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eig assumes that the linear map is not hermitian and returns type stable complex valued D and V tensors for both real and complex valued t. See eigh for hermitian linear maps

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eig!(t). Note that the permuted tensor on which eig! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

Accepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.

See also eigen and eigh.

source
LinearAlgebra.eigenFunction
eigen(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V

Compute eigenvalue factorization of tensor t as linear map from rightind to leftind.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigen!(t). Note that the permuted tensor on which eigen! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy

permute(t, (leftind, rightind)) * V = V * D

Accepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.

See also eig and eigh

source
LinearAlgebra.isposdefFunction
isposdef(t::AbstractTensor, (leftind, rightind)::Index2Tuple) -> ::Bool

Test whether a tensor t is positive definite as linear map from rightind to leftind.

If leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using isposdef!(t). Note that the permuted tensor on which isposdef! is called should have equal domain and codomain, as otherwise it is meaningless.

source

The corresponding destructive methods have an exclamation mark at the end of their name, and only accept the TensorMap object as well as the method-specific algorithm and keyword arguments.

TODO: document svd truncation types

diff --git a/previews/PR187/man/categories/index.html b/previews/PR187/man/categories/index.html index f6e9dbf2..445cc54b 100644 --- a/previews/PR187/man/categories/index.html +++ b/previews/PR187/man/categories/index.html @@ -6,4 +6,4 @@ Springer Science & Business Media.
  • kitaev
      Kitaev, A. (2006). Anyons in an exactly solved model and beyond.
             Annals of Physics, 321(1), 2-111.
  • beer
        From categories to anyons: a travelogue
             Kerstin Beer, Dmytro Bondarenko, Alexander Hahn, Maria Kalabakov, Nicole Knust, Laura Niermann, Tobias J. Osborne, Christin Schridde, Stefan Seckmeyer, Deniz E. Stiegemann, and Ramona Wolf
    -        [https://arxiv.org/abs/1811.06670](https://arxiv.org/abs/1811.06670)
  • + [https://arxiv.org/abs/1811.06670](https://arxiv.org/abs/1811.06670) diff --git a/previews/PR187/man/intro/index.html b/previews/PR187/man/intro/index.html index 8ddea8ca..5f0ca885 100644 --- a/previews/PR187/man/intro/index.html +++ b/previews/PR187/man/intro/index.html @@ -1,2 +1,2 @@ -Introduction · TensorKit.jl

    Introduction

    Before providing a typical "user guide" and discussing the implementation of TensorKit.jl on the next pages, let us discuss some of the rationale behind this package.

    What is a tensor?

    At the very start we should ponder about the most suitable and sufficiently general definition of a tensor. A good starting point is the following:

    • A tensor $t$ is an element from the tensor product of $N$ vector spaces $V_1 , V_2, …, V_N$, where $N$ is referred to as the rank or order of the tensor, i.e.

      $t ∈ V_1 ⊗ V_2 ⊗ … ⊗ V_N.$

    If you think of a tensor as an object with indices, a rank $N$ tensor has $N$ indices where every index is associated with the corresponding vector space in that it labels a particular basis in that space. We will return to index notation at the very end of this manual.

    As the tensor product of vector spaces is itself a vector space, this implies that a tensor behaves as a vector, i.e. tensors from the same tensor product space can be added and multiplied by scalars. The tensor product is only defined for vector spaces over the same field of scalars, e.g. there is no meaning in $ℝ^5 ⊗ ℂ^3$. When all the vector spaces in the tensor product have an inner product, this also implies an inner product for the tensor product space. It is hence clear that the different vector spaces in the tensor product should have some form of homogeneity in their structure, yet they do not need to be all equal and can e.g. have different dimensions. It goes without saying that defining the vector spaces and their properties will be an important part of the definition of a tensor. As a consequence, this also constitutes a significant part of the implementation, and is discussed in the section on Vector spaces.

    Aside from the interpretation of a tensor as a vector, we also want to interpret it as a matrix (or more correctly, a linear map) in order to decompose tensors using linear algebra factorisations (e.g. eigenvalue or singular value decomposition). Henceforth, we use the term "tensor map" as follows:

    • A tensor map $t$ is a linear map from a source or domain $W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2}$ to a target or codomain $V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}$, i.e.

      $t:W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2} → V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}.$

    A tensor of rank $N$ is then just a special case of a tensor map with $N_1 = N$ and $N_2 = 0$. A contraction between two tensors is just a composition of linear maps (i.e. matrix multiplication), where the contracted indices correspond to the domain of the first tensor and the codomain of the second tensor.

    In order to allow for arbitrary tensor contractions or decompositions, we need to be able to reorganise which vector spaces appear in the domain and the codomain of the tensor map, and in which order. This amounts to defining canonical isomorphisms between the different ways to order and partition the tensor indices (i.e. the vector spaces). For example, a linear map $W → V$ is often denoted as a rank 2 tensor in $V ⊗ W^*$, where $W^*$ corresponds to the dual space of $W$. This simple example introduces two new concepts.

    1. Typical vector spaces can appear in the domain and codomain in different related forms, e.g. as normal space or dual space. In fact, the most generic case is that every vector space $V$ has associated with it a dual space $V^*$, a conjugate space $\overline{V}$ and a conjugate dual space $\overline{V}^*$. The four different vector spaces $V$, $V^*$, $\overline{V}$ and $\overline{V}^*$ correspond to the representation spaces of respectively the fundamental, dual or contragredient, complex conjugate and dual complex conjugate representation of the general linear group $\mathsf{GL}(V)$. In index notation these spaces are denoted with respectively contravariant (upper), covariant (lower), dotted contravariant and dotted covariant indices.

      For real vector spaces, the conjugate (dual) space is identical to the normal (dual) space and we only have upper and lower indices, i.e. this is the setting of e.g. general relativity. For (complex) vector spaces with a sesquilinear inner product $\overline{V} ⊗ V → ℂ$, the inner product allows to define an isomorphism from the conjugate space to the dual space (known as Riesz representation theorem in the more general context of Hilbert spaces).

      In particular, in spaces with a Euclidean inner product (the setting of e.g. quantum mechanics), the conjugate and dual space are naturally isomorphic (because the dual and conjugate representation of the unitary group are the same). Again we only need upper and lower indices (or kets and bras).

      Finally, in $ℝ^d$ with a Euclidean inner product, these four different spaces are all equivalent and we only need one type of index. The space is completely characterized by its dimension $d$. This is the setting of much of classical mechanics and we refer to such tensors as cartesian tensors and the corresponding space as cartesian space. These are the tensors that can equally well be represented as multidimensional arrays (i.e. using some AbstractArray{<:Real,N} in Julia) without loss of structure.

      The implementation of all of this is discussed in Vector spaces.

    2. In the generic case, the identification between maps $W → V$ and tensors in $V ⊗ W^*$ is not an equivalence but an isomorphism, which needs to be defined. Similarly, there is an isomorphism between between $V ⊗ W$ and $W ⊗ V$ that can be non-trivial (e.g. in the case of fermions / super vector spaces). The correct formalism here is provided by theory of monoidal categories, which is introduced on the next page. Nonetheless, we try to hide these canonical isomorphisms from the user wherever possible, and one does not need to know category theory to be able to use this package.

    This brings us to our final (yet formal) definition

    • A tensor (map) is a homomorphism between two objects from the category $\mathbf{Vect}$ (or some subcategory thereof). In practice, this will be $\mathbf{FinVect}$, the category of finite dimensional vector spaces. More generally even, our concept of a tensor makes sense, in principle, for any linear (a.k.a. $\mathbf{Vect}$-enriched) monoidal category. We refer to the next page on "Monoidal categories and their properties".

    Symmetries and block sparsity

    Physical problems often have some symmetry, i.e. the setup is invariant under the action of a group $\mathsf{G}$ which acts on the vector spaces $V$ in the problem according to a certain representation. Having quantum mechanics in mind, TensorKit.jl is so far restricted to unitary representations. A general representation space $V$ can be specified as the number of times every irreducible representation (irrep) $a$ of $\mathsf{G}$ appears, i.e.

    $V = \bigoplus_{a} ℂ^{n_a} ⊗ R_a$

    with $R_a$ the space associated with irrep $a$ of $\mathsf{G}$, which itself has dimension $d_a$ (often called the quantum dimension), and $n_a$ the number of times this irrep appears in $V$. If the unitary irrep $a$ for $g ∈ \mathsf{G}$ is given by $u_a(g)$, then there exists a specific basis for $V$ such that the group action of $\mathsf{G}$ on $V$ is given by the unitary representation

    $u(g) = \bigoplus_{a} 𝟙_{n_a} ⊗ u_a(g)$

    with $𝟙_{n_a}$ the $n_a × n_a$ identity matrix. The total dimension of $V$ is given by $∑_a n_a d_a$.

    The reason for implementing symmetries is to exploit the computation and memory gains resulting from restricting to tensor maps $t:W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2} → V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}$ that are equivariant under the symmetry, i.e. that act as intertwiners between the symmetry action on the domain and the codomain. Indeed, such tensors should be block diagonal because of Schur's lemma, but only after we couple the individual irreps in the spaces $W_i$ to a joint irrep, which is then again split into the individual irreps of the spaces $V_i$. The basis change from the tensor product of irreps in the (co)domain to the joint irrep is implemented by a sequence of Clebsch–Gordan coefficients, also known as a fusion (or splitting) tree. We implement the necessary machinery to manipulate these fusion trees under index permutations and repartitions for arbitrary groups $\mathsf{G}$. In particular, this fits with the formalism of monoidal categories, and more specifically fusion categories, and only requires the topological data of the group, i.e. the fusion rules of the irreps, their quantum dimensions and the F-symbol (6j-symbol or more precisely Racah's W-symbol in the case of $\mathsf{SU}_2$). In particular, we don't actually need the Clebsch–Gordan coefficients themselves (but they can be useful for checking purposes).

    Hence, a second major part of TensorKit.jl is the interface and implementation for specifying symmetries, and further details are provided in Sectors, representation spaces and fusion trees.

    +Introduction · TensorKit.jl

    Introduction

    Before providing a typical "user guide" and discussing the implementation of TensorKit.jl on the next pages, let us discuss some of the rationale behind this package.

    What is a tensor?

    At the very start we should ponder about the most suitable and sufficiently general definition of a tensor. A good starting point is the following:

    • A tensor $t$ is an element from the tensor product of $N$ vector spaces $V_1 , V_2, …, V_N$, where $N$ is referred to as the rank or order of the tensor, i.e.

      $t ∈ V_1 ⊗ V_2 ⊗ … ⊗ V_N.$

    If you think of a tensor as an object with indices, a rank $N$ tensor has $N$ indices where every index is associated with the corresponding vector space in that it labels a particular basis in that space. We will return to index notation at the very end of this manual.

    As the tensor product of vector spaces is itself a vector space, this implies that a tensor behaves as a vector, i.e. tensors from the same tensor product space can be added and multiplied by scalars. The tensor product is only defined for vector spaces over the same field of scalars, e.g. there is no meaning in $ℝ^5 ⊗ ℂ^3$. When all the vector spaces in the tensor product have an inner product, this also implies an inner product for the tensor product space. It is hence clear that the different vector spaces in the tensor product should have some form of homogeneity in their structure, yet they do not need to be all equal and can e.g. have different dimensions. It goes without saying that defining the vector spaces and their properties will be an important part of the definition of a tensor. As a consequence, this also constitutes a significant part of the implementation, and is discussed in the section on Vector spaces.

    Aside from the interpretation of a tensor as a vector, we also want to interpret it as a matrix (or more correctly, a linear map) in order to decompose tensors using linear algebra factorisations (e.g. eigenvalue or singular value decomposition). Henceforth, we use the term "tensor map" as follows:

    • A tensor map $t$ is a linear map from a source or domain $W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2}$ to a target or codomain $V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}$, i.e.

      $t:W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2} → V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}.$

    A tensor of rank $N$ is then just a special case of a tensor map with $N_1 = N$ and $N_2 = 0$. A contraction between two tensors is just a composition of linear maps (i.e. matrix multiplication), where the contracted indices correspond to the domain of the first tensor and the codomain of the second tensor.

    In order to allow for arbitrary tensor contractions or decompositions, we need to be able to reorganise which vector spaces appear in the domain and the codomain of the tensor map, and in which order. This amounts to defining canonical isomorphisms between the different ways to order and partition the tensor indices (i.e. the vector spaces). For example, a linear map $W → V$ is often denoted as a rank 2 tensor in $V ⊗ W^*$, where $W^*$ corresponds to the dual space of $W$. This simple example introduces two new concepts.

    1. Typical vector spaces can appear in the domain and codomain in different related forms, e.g. as normal space or dual space. In fact, the most generic case is that every vector space $V$ has associated with it a dual space $V^*$, a conjugate space $\overline{V}$ and a conjugate dual space $\overline{V}^*$. The four different vector spaces $V$, $V^*$, $\overline{V}$ and $\overline{V}^*$ correspond to the representation spaces of respectively the fundamental, dual or contragredient, complex conjugate and dual complex conjugate representation of the general linear group $\mathsf{GL}(V)$. In index notation these spaces are denoted with respectively contravariant (upper), covariant (lower), dotted contravariant and dotted covariant indices.

      For real vector spaces, the conjugate (dual) space is identical to the normal (dual) space and we only have upper and lower indices, i.e. this is the setting of e.g. general relativity. For (complex) vector spaces with a sesquilinear inner product $\overline{V} ⊗ V → ℂ$, the inner product allows to define an isomorphism from the conjugate space to the dual space (known as Riesz representation theorem in the more general context of Hilbert spaces).

      In particular, in spaces with a Euclidean inner product (the setting of e.g. quantum mechanics), the conjugate and dual space are naturally isomorphic (because the dual and conjugate representation of the unitary group are the same). Again we only need upper and lower indices (or kets and bras).

      Finally, in $ℝ^d$ with a Euclidean inner product, these four different spaces are all equivalent and we only need one type of index. The space is completely characterized by its dimension $d$. This is the setting of much of classical mechanics and we refer to such tensors as cartesian tensors and the corresponding space as cartesian space. These are the tensors that can equally well be represented as multidimensional arrays (i.e. using some AbstractArray{<:Real,N} in Julia) without loss of structure.

      The implementation of all of this is discussed in Vector spaces.

    2. In the generic case, the identification between maps $W → V$ and tensors in $V ⊗ W^*$ is not an equivalence but an isomorphism, which needs to be defined. Similarly, there is an isomorphism between between $V ⊗ W$ and $W ⊗ V$ that can be non-trivial (e.g. in the case of fermions / super vector spaces). The correct formalism here is provided by theory of monoidal categories, which is introduced on the next page. Nonetheless, we try to hide these canonical isomorphisms from the user wherever possible, and one does not need to know category theory to be able to use this package.

    This brings us to our final (yet formal) definition

    • A tensor (map) is a homomorphism between two objects from the category $\mathbf{Vect}$ (or some subcategory thereof). In practice, this will be $\mathbf{FinVect}$, the category of finite dimensional vector spaces. More generally even, our concept of a tensor makes sense, in principle, for any linear (a.k.a. $\mathbf{Vect}$-enriched) monoidal category. We refer to the next page on "Monoidal categories and their properties".

    Symmetries and block sparsity

    Physical problems often have some symmetry, i.e. the setup is invariant under the action of a group $\mathsf{G}$ which acts on the vector spaces $V$ in the problem according to a certain representation. Having quantum mechanics in mind, TensorKit.jl is so far restricted to unitary representations. A general representation space $V$ can be specified as the number of times every irreducible representation (irrep) $a$ of $\mathsf{G}$ appears, i.e.

    $V = \bigoplus_{a} ℂ^{n_a} ⊗ R_a$

    with $R_a$ the space associated with irrep $a$ of $\mathsf{G}$, which itself has dimension $d_a$ (often called the quantum dimension), and $n_a$ the number of times this irrep appears in $V$. If the unitary irrep $a$ for $g ∈ \mathsf{G}$ is given by $u_a(g)$, then there exists a specific basis for $V$ such that the group action of $\mathsf{G}$ on $V$ is given by the unitary representation

    $u(g) = \bigoplus_{a} 𝟙_{n_a} ⊗ u_a(g)$

    with $𝟙_{n_a}$ the $n_a × n_a$ identity matrix. The total dimension of $V$ is given by $∑_a n_a d_a$.

    The reason for implementing symmetries is to exploit the computation and memory gains resulting from restricting to tensor maps $t:W_1 ⊗ W_2 ⊗ … ⊗ W_{N_2} → V_1 ⊗ V_2 ⊗ … ⊗ V_{N_1}$ that are equivariant under the symmetry, i.e. that act as intertwiners between the symmetry action on the domain and the codomain. Indeed, such tensors should be block diagonal because of Schur's lemma, but only after we couple the individual irreps in the spaces $W_i$ to a joint irrep, which is then again split into the individual irreps of the spaces $V_i$. The basis change from the tensor product of irreps in the (co)domain to the joint irrep is implemented by a sequence of Clebsch–Gordan coefficients, also known as a fusion (or splitting) tree. We implement the necessary machinery to manipulate these fusion trees under index permutations and repartitions for arbitrary groups $\mathsf{G}$. In particular, this fits with the formalism of monoidal categories, and more specifically fusion categories, and only requires the topological data of the group, i.e. the fusion rules of the irreps, their quantum dimensions and the F-symbol (6j-symbol or more precisely Racah's W-symbol in the case of $\mathsf{SU}_2$). In particular, we don't actually need the Clebsch–Gordan coefficients themselves (but they can be useful for checking purposes).

    Hence, a second major part of TensorKit.jl is the interface and implementation for specifying symmetries, and further details are provided in Sectors, representation spaces and fusion trees.

    diff --git a/previews/PR187/man/sectors/index.html b/previews/PR187/man/sectors/index.html index ae66a5fa..1ecf1528 100644 --- a/previews/PR187/man/sectors/index.html +++ b/previews/PR187/man/sectors/index.html @@ -234,7 +234,7 @@ FusionTree{Irrep[SU₂]}((1/2, 1/2, 1/2, 1/2, 1/2), 1/2, (true, false, false, true, false), (1, 1/2, 0)) FusionTree{Irrep[SU₂]}((1/2, 1/2, 1/2, 1/2, 1/2), 1/2, (true, false, false, true, false), (0, 1/2, 1)) FusionTree{Irrep[SU₂]}((1/2, 1/2, 1/2, 1/2, 1/2), 1/2, (true, false, false, true, false), (1, 1/2, 1)) - FusionTree{Irrep[SU₂]}((1/2, 1/2, 1/2, 1/2, 1/2), 1/2, (true, false, false, true, false), (1, 3/2, 1))
    julia> iter = fusiontrees(ntuple(n->s, 16))TensorKit.FusionTreeIterator{SU2Irrep, 16, NTuple{16, Tuple{SU2Irrep}}}(((Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),)), Irrep[SU₂](0), (false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false))
    julia> sum(n->1, iter)1430
    julia> length(iter)1430
    julia> @elapsed sum(n->1, iter)0.06184278
    julia> @elapsed length(iter)4.6897e-5
    julia> s2 = s ⊠ sIrrep[SU₂ × SU₂](1/2, 1/2)
    julia> collect(fusiontrees((s2,s2,s2,s2)))4-element Vector{FusionTree{ProductSector{Tuple{SU2Irrep, SU2Irrep}}, 4, 2, 3}}: + FusionTree{Irrep[SU₂]}((1/2, 1/2, 1/2, 1/2, 1/2), 1/2, (true, false, false, true, false), (1, 3/2, 1))
    julia> iter = fusiontrees(ntuple(n->s, 16))TensorKit.FusionTreeIterator{SU2Irrep, 16, NTuple{16, Tuple{SU2Irrep}}}(((Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),), (Irrep[SU₂](1/2),)), Irrep[SU₂](0), (false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false))
    julia> sum(n->1, iter)1430
    julia> length(iter)1430
    julia> @elapsed sum(n->1, iter)0.052836663
    julia> @elapsed length(iter)4.1907e-5
    julia> s2 = s ⊠ sIrrep[SU₂ × SU₂](1/2, 1/2)
    julia> collect(fusiontrees((s2,s2,s2,s2)))4-element Vector{FusionTree{ProductSector{Tuple{SU2Irrep, SU2Irrep}}, 4, 2, 3}}: FusionTree{Irrep[SU₂ × SU₂]}(((1/2, 1/2), (1/2, 1/2), (1/2, 1/2), (1/2, 1/2)), (0, 0), (false, false, false, false), ((0, 0), (1/2, 1/2))) FusionTree{Irrep[SU₂ × SU₂]}(((1/2, 1/2), (1/2, 1/2), (1/2, 1/2), (1/2, 1/2)), (0, 0), (false, false, false, false), ((1, 0), (1/2, 1/2))) FusionTree{Irrep[SU₂ × SU₂]}(((1/2, 1/2), (1/2, 1/2), (1/2, 1/2), (1/2, 1/2)), (0, 0), (false, false, false, false), ((0, 1), (1/2, 1/2))) @@ -315,4 +315,4 @@ 0.618034 0.786151 0.786151 -0.618034
    julia> Fτ'*Fτ2×2 Matrix{Float64}: 1.0 0.0 - 0.0 1.0
    julia> polar(x) = rationalize.((abs(x), angle(x)/(2pi)))polar (generic function with 1 method)
    julia> Rsymbol(τ,τ,𝟙) |> polar(1//1, 2//5)
    julia> Rsymbol(τ,τ,τ) |> polar(1//1, -3//10)
    julia> twist(τ) |> polar(1//1, -2//5) + 0.0 1.0
    julia> polar(x) = rationalize.((abs(x), angle(x)/(2pi)))polar (generic function with 1 method)
    julia> Rsymbol(τ,τ,𝟙) |> polar(1//1, 2//5)
    julia> Rsymbol(τ,τ,τ) |> polar(1//1, -3//10)
    julia> twist(τ) |> polar(1//1, -2//5) diff --git a/previews/PR187/man/spaces/index.html b/previews/PR187/man/spaces/index.html index f0f7b1d0..977e5f65 100644 --- a/previews/PR187/man/spaces/index.html +++ b/previews/PR187/man/spaces/index.html @@ -8,7 +8,7 @@ struct ComplexNumbers <: Field end const ℝ = RealNumbers() -const ℂ = ComplexNumbers()

    Note that and can be typed as \bbR+TAB and \bbC+TAB. One reason for defining this new type hierarchy instead of recycling the types from Julia's Number hierarchy is to introduce some syntactic sugar without committing type piracy. In particular, we now have

    julia> 3 ∈ ℝtrue
    julia> 5.0 ∈ ℂtrue
    julia> 5.0+1.0*im ∈ ℝfalse
    julia> Float64 ⊆ ℝtrue
    julia> ComplexF64 ⊆ ℂtrue
    julia> ℝ ⊆ ℂtrue
    julia> ℂ ⊆ ℝfalse

    and furthermore —probably more usefully— ℝ^n and ℂ^n create specific elementary vector spaces as described in the next section. The underlying field of a vector space or tensor a can be obtained with field(a):

    TensorKit.fieldFunction
    field(V::VectorSpace) -> Field

    Return the field type over which a vector space is defined.

    source

    Elementary spaces

    As mentioned at the beginning of this section, vector spaces that are associated with the individual indices of a tensor should be implemented as subtypes of ElementarySpace. As the domain and codomain of a tensor map will be the tensor product of such objects which all have the same type, it is important that related vector spaces, e.g. the dual space, are objects of the same concrete type (i.e. with the same type parameters in case of a parametric type). In particular, every ElementarySpace should implement the following methods

    For convenience, the dual of a space V can also be obtained as V'.

    There is concrete type GeneralSpace which is completely characterized by its field 𝔽, its dimension and whether its the dual and/or complex conjugate of $𝔽^d$.

    struct GeneralSpace{𝔽} <: ElementarySpace
    +const ℂ = ComplexNumbers()

    Note that and can be typed as \bbR+TAB and \bbC+TAB. One reason for defining this new type hierarchy instead of recycling the types from Julia's Number hierarchy is to introduce some syntactic sugar without committing type piracy. In particular, we now have

    julia> 3 ∈ ℝtrue
    julia> 5.0 ∈ ℂtrue
    julia> 5.0+1.0*im ∈ ℝfalse
    julia> Float64 ⊆ ℝtrue
    julia> ComplexF64 ⊆ ℂtrue
    julia> ℝ ⊆ ℂtrue
    julia> ℂ ⊆ ℝfalse

    and furthermore —probably more usefully— ℝ^n and ℂ^n create specific elementary vector spaces as described in the next section. The underlying field of a vector space or tensor a can be obtained with field(a):

    TensorKit.fieldFunction
    field(V::VectorSpace) -> Field

    Return the field type over which a vector space is defined.

    source

    Elementary spaces

    As mentioned at the beginning of this section, vector spaces that are associated with the individual indices of a tensor should be implemented as subtypes of ElementarySpace. As the domain and codomain of a tensor map will be the tensor product of such objects which all have the same type, it is important that related vector spaces, e.g. the dual space, are objects of the same concrete type (i.e. with the same type parameters in case of a parametric type). In particular, every ElementarySpace should implement the following methods

    For convenience, the dual of a space V can also be obtained as V'.

    There is concrete type GeneralSpace which is completely characterized by its field 𝔽, its dimension and whether its the dual and/or complex conjugate of $𝔽^d$.

    struct GeneralSpace{𝔽} <: ElementarySpace
         d::Int
         dual::Bool
         conj::Bool
    @@ -27,4 +27,4 @@
         codomain::P1
         domain::P2
     end

    and can create it as either domain → codomain or codomain ← domain (where the arrows are obtained as \to+TAB or \leftarrow+TAB, and as \rightarrow+TAB respectively). The reason for first listing the codomain and than the domain will become clear in the section on tensor maps.

    Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct. However, HomSpace has a number of properties defined, which we illustrate via examples

    julia> W = ℂ^2 ⊗ ℂ^3 → ℂ^3 ⊗ dual(ℂ^4)(ℂ^3 ⊗ (ℂ^4)') ← (ℂ^2 ⊗ ℂ^3)
    julia> field(W)
    julia> dual(W)((ℂ^3)' ⊗ (ℂ^2)') ← (ℂ^4 ⊗ (ℂ^3)')
    julia> adjoint(W)(ℂ^2 ⊗ ℂ^3) ← (ℂ^3 ⊗ (ℂ^4)')
    julia> spacetype(W)ComplexSpace
    julia> spacetype(typeof(W))ComplexSpace
    julia> W[1]ℂ^3
    julia> W[2](ℂ^4)'
    julia> W[3](ℂ^2)'
    julia> W[4](ℂ^3)'
    julia> dim(W)72

    Note that indexing W yields first the spaces in the codomain, followed by the dual of the spaces in the domain. This particular convention is useful in combination with the instances of type TensorMap, which represent morphisms living in such a HomSpace. Also note that dim(W) here seems to be the product of the dimensions of the individual spaces, but that this is no longer true once symmetries are involved. At any time will dim(::HomSpace) represent the number of linearly independent morphisms in this space.

    Partial order among vector spaces

    Vector spaces of the same spacetype can be given a partial order, based on whether there exist injective morphisms (a.k.a monomorphisms) or surjective morphisms (a.k.a. epimorphisms) between them. In particular, we define ismonomorphic(V1, V2), with Unicode synonym V1 ≾ V2 (obtained as \precsim+TAB), to express whether there exist monomorphisms in V1→V2. Similarly, we define isepimorphic(V1, V2), with Unicode synonym V1 ≿ V2 (obtained as \succsim+TAB), to express whether there exist epimorphisms in V1→V2. Finally, we define isisomorphic(V1, V2), with Unicode alternative V1 ≅ V2 (obtained as \cong+TAB), to express whether there exist isomorphism in V1→V2. In particular V1 ≅ V2 if and only if V1 ≾ V2 && V1 ≿ V2.

    For completeness, we also export the strict comparison operators and (\prec+TAB and \succ+TAB), with definitions

    ≺(V1::VectorSpace, V2::VectorSpace) = V1 ≾ V2 && !(V1 ≿ V2)
    -≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2)

    However, as we expect these to be less commonly used, no ASCII alternative is provided.

    In the context of InnerProductStyle(V) <: EuclideanInnerProduct, V1 ≾ V2 implies that there exists isometries $W:V1 → V2$ such that $W^† ∘ W = \mathrm{id}_{V1}$, while V1 ≅ V2 implies that there exist unitaries $U:V1→V2$ such that $U^† ∘ U = \mathrm{id}_{V1}$ and $U ∘ U^† = \mathrm{id}_{V2}$.

    Note that spaces that are isomorphic are not necessarily equal. One can be a dual space, and the other a normal space, or one can be an instance of ProductSpace, while the other is an ElementarySpace. There will exist (infinitely) many isomorphisms between the corresponding spaces, but in general none of those will be canonical.

    There are also a number of convenience functions to create isomorphic spaces. The function fuse(V1, V2, ...) or fuse(V1 ⊗ V2 ⊗ ...) returns an elementary space that is isomorphic to V1 ⊗ V2 ⊗ .... The function flip(V::ElementarySpace) returns a space that is isomorphic to V but has isdual(flip(V)) == isdual(V'), i.e. if V is a normal space than flip(V) is a dual space. flip(V) is different from dual(V) in the case of GradedSpace. It is useful to flip a tensor index from a ket to a bra (or vice versa), by contracting that index with a unitary map from V1 to flip(V1). We refer to the reference on vector space methods for further information. Some examples:

    julia> ℝ^3 ≾ ℝ^5true
    julia> ℂ^3 ≾ (ℂ^5)'true
    julia> (ℂ^5) ≅ (ℂ^5)'true
    julia> fuse(ℝ^5, ℝ^3)ℝ^15
    julia> fuse(ℂ^3, (ℂ^5)' ⊗ ℂ^2)ℂ^30
    julia> fuse(ℂ^3, (ℂ^5)') ⊗ ℂ^2 ≅ fuse(ℂ^3, (ℂ^5)', ℂ^2) ≅ ℂ^3 ⊗ (ℂ^5)' ⊗ ℂ^2true
    julia> flip(ℂ^4)(ℂ^4)'
    julia> flip(ℂ^4) ≅ ℂ^4true
    julia> flip(ℂ^4) == ℂ^4false

    We also define the direct sum V1 and V2 as V1 ⊕ V2, where is obtained by typing \oplus+TAB. This is possible only if isdual(V1) == isdual(V2). With a little pun on Julia Base, oneunit applied to an elementary space (in the value or type domain) returns the one-dimensional space, which is isomorphic to the scalar field of the space itself. Some examples illustrate this better

    julia> ℝ^5 ⊕ ℝ^3ℝ^8
    julia> ℂ^5 ⊕ ℂ^3ℂ^8
    julia> ℂ^5 ⊕ (ℂ^3)'ERROR: SpaceMismatch("Direct sum of a vector space and its dual does not exist")
    julia> oneunit(ℝ^3)ℝ^1
    julia> ℂ^5 ⊕ oneunit(ComplexSpace)ℂ^6
    julia> oneunit((ℂ^3)')ℂ^1
    julia> (ℂ^5) ⊕ oneunit((ℂ^5))ℂ^6
    julia> (ℂ^5)' ⊕ oneunit((ℂ^5)')ERROR: SpaceMismatch("Direct sum of a vector space and its dual does not exist")

    Finally, while spaces have a partial order, there is no unique infimum or supremum of a two or more spaces. However, if V1 and V2 are two ElementarySpace instances with isdual(V1) == isdual(V2), then we can define a unique infimum V::ElementarySpace with the same value of isdual that satisfies V ≾ V1 and V ≾ V2, as well as a unique supremum W::ElementarySpace with the same value of isdual that satisfies W ≿ V1 and W ≿ V2. For CartesianSpace and ComplexSpace, this simply amounts to the space with minimal or maximal dimension, i.e.

    julia> infimum(ℝ^5, ℝ^3)ℝ^3
    julia> supremum(ℂ^5, ℂ^3)ℂ^5
    julia> supremum(ℂ^5, (ℂ^3)')ERROR: SpaceMismatch("Supremum of space and dual space does not exist")

    The names infimum and supremum are especially suited in the case of GradedSpace, as the infimum of two spaces might be different from either of those two spaces, and similar for the supremum.

    +≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2)

    However, as we expect these to be less commonly used, no ASCII alternative is provided.

    In the context of InnerProductStyle(V) <: EuclideanInnerProduct, V1 ≾ V2 implies that there exists isometries $W:V1 → V2$ such that $W^† ∘ W = \mathrm{id}_{V1}$, while V1 ≅ V2 implies that there exist unitaries $U:V1→V2$ such that $U^† ∘ U = \mathrm{id}_{V1}$ and $U ∘ U^† = \mathrm{id}_{V2}$.

    Note that spaces that are isomorphic are not necessarily equal. One can be a dual space, and the other a normal space, or one can be an instance of ProductSpace, while the other is an ElementarySpace. There will exist (infinitely) many isomorphisms between the corresponding spaces, but in general none of those will be canonical.

    There are also a number of convenience functions to create isomorphic spaces. The function fuse(V1, V2, ...) or fuse(V1 ⊗ V2 ⊗ ...) returns an elementary space that is isomorphic to V1 ⊗ V2 ⊗ .... The function flip(V::ElementarySpace) returns a space that is isomorphic to V but has isdual(flip(V)) == isdual(V'), i.e. if V is a normal space than flip(V) is a dual space. flip(V) is different from dual(V) in the case of GradedSpace. It is useful to flip a tensor index from a ket to a bra (or vice versa), by contracting that index with a unitary map from V1 to flip(V1). We refer to the reference on vector space methods for further information. Some examples:

    julia> ℝ^3 ≾ ℝ^5true
    julia> ℂ^3 ≾ (ℂ^5)'true
    julia> (ℂ^5) ≅ (ℂ^5)'true
    julia> fuse(ℝ^5, ℝ^3)ℝ^15
    julia> fuse(ℂ^3, (ℂ^5)' ⊗ ℂ^2)ℂ^30
    julia> fuse(ℂ^3, (ℂ^5)') ⊗ ℂ^2 ≅ fuse(ℂ^3, (ℂ^5)', ℂ^2) ≅ ℂ^3 ⊗ (ℂ^5)' ⊗ ℂ^2true
    julia> flip(ℂ^4)(ℂ^4)'
    julia> flip(ℂ^4) ≅ ℂ^4true
    julia> flip(ℂ^4) == ℂ^4false

    We also define the direct sum V1 and V2 as V1 ⊕ V2, where is obtained by typing \oplus+TAB. This is possible only if isdual(V1) == isdual(V2). With a little pun on Julia Base, oneunit applied to an elementary space (in the value or type domain) returns the one-dimensional space, which is isomorphic to the scalar field of the space itself. Some examples illustrate this better

    julia> ℝ^5 ⊕ ℝ^3ℝ^8
    julia> ℂ^5 ⊕ ℂ^3ℂ^8
    julia> ℂ^5 ⊕ (ℂ^3)'ERROR: SpaceMismatch("Direct sum of a vector space and its dual does not exist")
    julia> oneunit(ℝ^3)ℝ^1
    julia> ℂ^5 ⊕ oneunit(ComplexSpace)ℂ^6
    julia> oneunit((ℂ^3)')ℂ^1
    julia> (ℂ^5) ⊕ oneunit((ℂ^5))ℂ^6
    julia> (ℂ^5)' ⊕ oneunit((ℂ^5)')ERROR: SpaceMismatch("Direct sum of a vector space and its dual does not exist")

    Finally, while spaces have a partial order, there is no unique infimum or supremum of a two or more spaces. However, if V1 and V2 are two ElementarySpace instances with isdual(V1) == isdual(V2), then we can define a unique infimum V::ElementarySpace with the same value of isdual that satisfies V ≾ V1 and V ≾ V2, as well as a unique supremum W::ElementarySpace with the same value of isdual that satisfies W ≿ V1 and W ≿ V2. For CartesianSpace and ComplexSpace, this simply amounts to the space with minimal or maximal dimension, i.e.

    julia> infimum(ℝ^5, ℝ^3)ℝ^3
    julia> supremum(ℂ^5, ℂ^3)ℂ^5
    julia> supremum(ℂ^5, (ℂ^3)')ERROR: SpaceMismatch("Supremum of space and dual space does not exist")

    The names infimum and supremum are especially suited in the case of GradedSpace, as the infimum of two spaces might be different from either of those two spaces, and similar for the supremum.

    diff --git a/previews/PR187/man/tensors/index.html b/previews/PR187/man/tensors/index.html index 501b5cef..18cb9596 100644 --- a/previews/PR187/man/tensors/index.html +++ b/previews/PR187/man/tensors/index.html @@ -6,12 +6,12 @@ Tensor(undef, codomain) Tensor(undef, eltype::Type{<:Number}, codomain)

    Here, f is any of the typical functions from Base that normally create arrays, namely zeros, ones, rand, randn and Random.randexp. Remember that one(codomain) is the empty ProductSpace{S,0}(). The third and fourth calling syntax use the UndefInitializer from Julia Base and generates a TensorMap with unitialized data, which can thus contain NaNs.

    In all of these constructors, the last two arguments can be replaced by domain→codomain or codomain←domain, where the arrows are obtained as \rightarrow+TAB and \leftarrow+TAB and create a HomSpace as explained in the section on Spaces of morphisms. Some examples are perhaps in order

    julia> t1 = randn(ℂ^2 ⊗ ℂ^3, ℂ^2)TensorMap((ℂ^2 ⊗ ℂ^3) ← ℂ^2):
     [:, :, 1] =
    - -0.26602406332265893  0.8041051873834778    0.2740166247217533
    - -0.3522930695581344   0.38072948159402775  -2.645226017981021
    +  0.2133806764709652   0.7931845520265705   0.2060852344968069
    + -1.092560998112074   -0.5655949045825904  -0.41861565304463066
     
     [:, :, 2] =
    - 1.955898913001859   1.1717347997619003  -0.02900174998166497
    - 0.6924614357108203  0.320461381033448    0.605336231644514
    julia> t2 = zeros(Float32, ℂ^2 ⊗ ℂ^3 ← ℂ^2)TensorMap((ℂ^2 ⊗ ℂ^3) ← ℂ^2): + 0.746885162750703 2.1599667176301054 0.44739607261875447 + -0.17824890837905524 -0.6233683754899755 0.5441697505077127
    julia> t2 = zeros(Float32, ℂ^2 ⊗ ℂ^3 ← ℂ^2)TensorMap((ℂ^2 ⊗ ℂ^3) ← ℂ^2): [:, :, 1] = 0.0f0 0.0f0 0.0f0 0.0f0 0.0f0 0.0f0 @@ -22,34 +22,34 @@ The type `TensorMap` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: - TensorMap(::typeof(randnormal), ::HomSpace) + TensorMap(::typeof(randuniform), ::HomSpace) @ TensorKit deprecated.jl:103 TensorMap(::typeof(randn), ::HomSpace) @ TensorKit deprecated.jl:103 - TensorMap(::typeof(ones), ::HomSpace) + TensorMap(::typeof(randhaar), ::HomSpace) @ TensorKit deprecated.jl:103 ...
    julia> domain(t1) == domain(t2) == domain(t3)ERROR: UndefVarError: `t3` not defined in `Main` Suggestion: check for spelling errors or missing imports.
    julia> codomain(t1) == codomain(t2) == codomain(t3)ERROR: UndefVarError: `t3` not defined in `Main` Suggestion: check for spelling errors or missing imports.
    julia> disp(x) = show(IOContext(Core.stdout, :compact=>false), "text/plain", trunc.(x; digits = 3));
    julia> t1[] |> disp2×3×2 StridedViews.StridedView{Float64, 3, Array{Float64, 3}, typeof(identity)}: [:, :, 1] = - -0.266 0.804 0.274 - -0.352 0.38 -2.645 + 0.213 0.793 0.206 + -1.092 -0.565 -0.418 [:, :, 2] = - 1.955 1.171 -0.029 - 0.692 0.32 0.605
    julia> block(t1, Trivial()) |> disp6×2 Array{Float64, 2}: - -0.266 1.955 - -0.352 0.692 - 0.804 1.171 - 0.38 0.32 - 0.274 -0.029 - -2.645 0.605
    julia> reshape(t1[], dim(codomain(t1)), dim(domain(t1))) |> disp6×2 Array{Float64, 2}: - -0.266 1.955 - -0.352 0.692 - 0.804 1.171 - 0.38 0.32 - 0.274 -0.029 - -2.645 0.605

    Finally, all constructors can also be replaced by Tensor(..., codomain), in which case the domain is assumed to be the empty ProductSpace{S,0}(), which can easily be obtained as one(codomain). Indeed, the empty product space is the unit object of the monoidal category, equivalent to the field of scalars 𝕜, and thus the multiplicative identity (especially since * also acts as tensor product on vector spaces).

    The matrices created by f are the matrices $B_c$ discussed above, i.e. those returned by block(t, c). Only numerical matrices of type DenseMatrix are accepted, which in practice just means Julia's intrinsic Matrix{T} for some T<:Number. In the future, we will add support for CuMatrix from CuArrays.jl to harness GPU computing power, and maybe SharedArray from the Julia's SharedArrays standard library.

    Support for static or sparse data is currently unavailable, and if it would be implemented, it would lead to new subtypes of AbstractTensorMap which are distinct from TensorMap. Future implementations of e.g. SparseTensorMap or StaticTensorMap could be useful. Furthermore, there could be specific implementations for tensors whose blocks are Diagonal.

    Tensor maps from existing data

    To create a TensorMap with existing data, one can use the aforementioned form but with the function f replaced with the actual data, i.e. TensorMap(data, codomain, domain) or any of its equivalents.

    Here, data can be of two types. It can be a dictionary (any Associative subtype) which has blocksectors c of type sectortype(codomain) as keys, and the corresponding matrix blocks as value, i.e. data[c] is some DenseMatrix of size (blockdim(codomain, c), blockdim(domain, c)). This is the form of how the data is stored within the TensorMap objects.

    For those space types for which a TensorMap can be converted to a plain multidimensional array, the data can also be a general DenseArray, either of rank N₁+N₂ and with matching size (dims(codomain)..., dims(domain)...), or just as a DenseMatrix with size (dim(codomain), dim(domain)). This is true in particular if the sector type is Trivial, e.g. for CartesianSpace or ComplexSpace. Then the data array is just reshaped into matrix form and referred to as such in the resulting TensorMap instance. When spacetype is GradedSpace, the TensorMap constructor will try to reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t). This might not be possible, if the data does not respect the symmetry structure. This procedure can be sketched using a simple physical example, namely the SWAP gate on two qubits,

    \[\begin{align*} + 0.746 2.159 0.447 + -0.178 -0.623 0.544
    julia> block(t1, Trivial()) |> disp6×2 Array{Float64, 2}: + 0.213 0.746 + -1.092 -0.178 + 0.793 2.159 + -0.565 -0.623 + 0.206 0.447 + -0.418 0.544
    julia> reshape(t1[], dim(codomain(t1)), dim(domain(t1))) |> disp6×2 Array{Float64, 2}: + 0.213 0.746 + -1.092 -0.178 + 0.793 2.159 + -0.565 -0.623 + 0.206 0.447 + -0.418 0.544

    Finally, all constructors can also be replaced by Tensor(..., codomain), in which case the domain is assumed to be the empty ProductSpace{S,0}(), which can easily be obtained as one(codomain). Indeed, the empty product space is the unit object of the monoidal category, equivalent to the field of scalars 𝕜, and thus the multiplicative identity (especially since * also acts as tensor product on vector spaces).

    The matrices created by f are the matrices $B_c$ discussed above, i.e. those returned by block(t, c). Only numerical matrices of type DenseMatrix are accepted, which in practice just means Julia's intrinsic Matrix{T} for some T<:Number. In the future, we will add support for CuMatrix from CuArrays.jl to harness GPU computing power, and maybe SharedArray from the Julia's SharedArrays standard library.

    Support for static or sparse data is currently unavailable, and if it would be implemented, it would lead to new subtypes of AbstractTensorMap which are distinct from TensorMap. Future implementations of e.g. SparseTensorMap or StaticTensorMap could be useful. Furthermore, there could be specific implementations for tensors whose blocks are Diagonal.

    Tensor maps from existing data

    To create a TensorMap with existing data, one can use the aforementioned form but with the function f replaced with the actual data, i.e. TensorMap(data, codomain, domain) or any of its equivalents.

    Here, data can be of two types. It can be a dictionary (any Associative subtype) which has blocksectors c of type sectortype(codomain) as keys, and the corresponding matrix blocks as value, i.e. data[c] is some DenseMatrix of size (blockdim(codomain, c), blockdim(domain, c)). This is the form of how the data is stored within the TensorMap objects.

    For those space types for which a TensorMap can be converted to a plain multidimensional array, the data can also be a general DenseArray, either of rank N₁+N₂ and with matching size (dims(codomain)..., dims(domain)...), or just as a DenseMatrix with size (dim(codomain), dim(domain)). This is true in particular if the sector type is Trivial, e.g. for CartesianSpace or ComplexSpace. Then the data array is just reshaped into matrix form and referred to as such in the resulting TensorMap instance. When spacetype is GradedSpace, the TensorMap constructor will try to reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t). This might not be possible, if the data does not respect the symmetry structure. This procedure can be sketched using a simple physical example, namely the SWAP gate on two qubits,

    \[\begin{align*} \mathrm{SWAP}: \mathbb{C}^2 \otimes \mathbb{C}^2 & \to \mathbb{C}^2 \otimes \mathbb{C}^2\\ |i\rangle \otimes |j\rangle &\mapsto |j\rangle \otimes |i\rangle. \end{align*}\]

    This operator can be rewritten in terms of the familiar Heisenberg exchange interaction $\vec{S}_i \cdot \vec{S}_j$ as

    \[\mathrm{SWAP} = 2 \vec{S}_i \cdot \vec{S}_j + \frac{1}{2} 𝟙,\]

    where $\vec{S} = (S^x, S^y, S^z)$ and the spin-1/2 generators of SU₂ $S^k$ are defined defined in terms of the $2 \times 2$ Pauli matrices $\sigma^k$ as $S^k = \frac{1}{2}\sigma^k$. The SWAP gate can be realized as a rank-4 TensorMap in the following way:

    julia> # encode the matrix elements of the swap gate into a rank-4 array, where the first two
    @@ -149,192 +149,192 @@
      1.0

    Constructing similar tensors

    A third way to construct a TensorMap instance is to use Base.similar, i.e.

    similar(t [, T::Type{<:Number}, codomain, domain])

    where T is a possibly different eltype for the tensor data, and codomain and domain optionally define a new codomain and domain for the resulting tensor. By default, these values just take the value from the input tensor t. The result will be a new TensorMap instance, with undef data, but whose data is stored in the same subtype of DenseMatrix (e.g. Matrix or CuMatrix or ...) as t. In particular, this uses the methods storagetype(t) and TensorKit.similarstoragetype(t, T).

    Special purpose constructors

    Finally, there are methods zero, one, id, isomorphism, unitary and isometry to create specific new tensors. Tensor maps behave as vectors and can be added (if they have the same domain and codomain); zero(t) is the additive identity, i.e. a TensorMap instance where all entries are zero. For a t::TensorMap with domain(t) == codomain(t), i.e. an endomorphism, one(t) creates the identity tensor, i.e. the identity under composition. As discussed in the section on linear algebra operations, we denote composition of tensor maps with the mutliplication operator *, such that one(t) is the multiplicative identity. Similarly, it can be created as id(V) with V the relevant vector space, e.g. one(t) == id(domain(t)). The identity tensor is currently represented with dense data, and one can use id(A::Type{<:DenseMatrix}, V) to specify the type of DenseMatrix (and its eltype), e.g. A = Matrix{Float64}. Finally, it often occurs that we want to construct a specific isomorphism between two spaces that are isomorphic but not equal, and for which there is no canonical choice. Hereto, one can use the method u = isomorphism([A::Type{<:DenseMatrix}, ] codomain, domain), which will explicitly check that the domain and codomain are isomorphic, and return an error otherwise. Again, an optional first argument can be given to specify the specific type of DenseMatrix that is currently used to store the rather trivial data of this tensor. If InnerProductStyle(u) <: EuclideanProduct, the same result can be obtained with the method u = unitary([A::Type{<:DenseMatrix}, ] codomain, domain). Note that reversing the domain and codomain yields the inverse morphism, which in the case of EuclideanProduct coincides with the adjoint morphism, i.e. isomorphism(A, domain, codomain) == adjoint(u) == inv(u), where inv and adjoint will be further discussed below. Finally, if two spaces V1 and V2 are such that V2 can be embedded in V1, i.e. there exists an inclusion with a left inverse, and furthermore they represent tensor products of some ElementarySpace with EuclideanProduct, the function w = isometry([A::Type{<:DenseMatrix}, ], V1, V2) creates one specific isometric embedding, such that adjoint(w) * w == id(V2) and w * adjoint(w) is some hermitian idempotent (a.k.a. orthogonal projector) acting on V1. An error will be thrown if such a map cannot be constructed for the given domain and codomain.

    Let's conclude this section with some examples with GradedSpace.

    julia> V1 = ℤ₂Space(0=>3,1=>2)Rep[ℤ₂](0=>3, 1=>2)
    julia> V2 = ℤ₂Space(0=>2,1=>1) # First a `TensorMap{ℤ₂Space, 1, 1}`Rep[ℤ₂](0=>2, 1=>1)
    julia> m = randn(V1, V2)TensorMap(Rep[ℤ₂](0=>3, 1=>2) ← Rep[ℤ₂](0=>2, 1=>1)): * Data for sector (Irrep[ℤ₂](0),) ← (Irrep[ℤ₂](0),): - 0.04893722564346503 1.5858830432940374 - -0.3160057929779539 1.0103809178750296 - 0.06639049704569353 0.22018217599228274 + -1.2887401049867813 0.13908780994927672 + 0.019334631500676106 -1.5080553631231832 + 0.46491250058155065 0.15741604440289203 * Data for sector (Irrep[ℤ₂](1),) ← (Irrep[ℤ₂](1),): - -0.36915309612887964 - -0.060189907996407624
    julia> convert(Array, m) |> disp + -0.13998331591488464 + 1.4374562905016453
    julia> convert(Array, m) |> disp # compare with:5×3 Array{Float64, 2}: - 0.048 1.585 0.0 - -0.316 1.01 0.0 - 0.066 0.22 0.0 - 0.0 0.0 -0.369 - 0.0 0.0 -0.06
    julia> block(m, Irrep[ℤ₂](0)) |> disp3×2 Array{Float64, 2}: - 0.048 1.585 - -0.316 1.01 - 0.066 0.22
    julia> block(m, Irrep[ℤ₂](1)) |> disp + -1.288 0.139 0.0 + 0.019 -1.508 0.0 + 0.464 0.157 0.0 + 0.0 0.0 -0.139 + 0.0 0.0 1.437
    julia> block(m, Irrep[ℤ₂](0)) |> disp3×2 Array{Float64, 2}: + -1.288 0.139 + 0.019 -1.508 + 0.464 0.157
    julia> block(m, Irrep[ℤ₂](1)) |> disp # Now a `TensorMap{ℤ₂Space, 2, 2}`2×1 Array{Float64, 2}: - -0.369 - -0.06
    julia> t = randn(V1 ⊗ V1, V2 ⊗ V2')TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2)) ← (Rep[ℤ₂](0=>2, 1=>1) ⊗ Rep[ℤ₂](0=>2, 1=>1)')): + -0.139 + 1.437
    julia> t = randn(V1 ⊗ V1, V2 ⊗ V2')TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2)) ← (Rep[ℤ₂](0=>2, 1=>1) ⊗ Rep[ℤ₂](0=>2, 1=>1)')): * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](0)): [:, :, 1, 1] = - 0.7915549836073429 1.091839028960968 -0.5004285493974157 - 0.3933030226267141 -0.8573058494338133 0.17171811142668167 - -0.5026811763807939 1.0229689617502773 -0.7969355951155274 + -0.6246879876347943 -0.44579621555904964 0.2714328757005388 + -0.3916223087812196 -0.14133762857444404 1.492039348724011 + 0.29300343511459526 -0.03510407972307768 -0.6080923145577396 [:, :, 2, 1] = - 1.5789673443158694 0.10678441642881759 1.5267055725508758 - 2.4558877910103987 -2.2507458077668168 0.992436486297559 - 0.6120171023290746 -0.36148313677255456 0.57586670846593 + 0.4776454022766807 -0.19763916052342673 -2.810798347055642 + -1.0703003120150105 -0.731640758312332 -1.2265521812270463 + -0.7633777085223793 2.0263913913377305 -0.7798422865435699 [:, :, 1, 2] = - 0.12502435787434218 -0.10351959503240597 0.2182430303931846 - 1.0768664712523748 -1.7488248629641079 -0.188147821190649 - -1.0254001672571063 0.9247870984812319 -0.2984357250081701 + -0.4337999040774438 0.9407150815536591 0.6820309603737881 + 0.40920075791669647 -0.41142217066411957 1.0845714558497335 + -1.3086633134355394 -1.2205754605956554 0.12312121350003671 [:, :, 2, 2] = - -1.39929431385248 0.6320055944602623 -0.5391901351426786 - -1.18421119849474 0.4075583164351105 0.2731641938274981 - 1.5665182732077123 0.8027216089587178 0.9960476938319369 + -0.8705348074498453 -0.6773348533736817 0.1806308201490006 + -1.3785497821936534 -0.5649913151801983 -0.016960053044038262 + -0.6086409514319936 -0.15950345534095073 -1.3486391691009891 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](0)): [:, :, 1, 1] = - -0.32229107496134474 1.2237033172119363 - 0.2894325671279399 -0.4624828671150314 + 0.7251873827509915 -0.11526188345569144 + -0.722198792927218 1.2107659552997045 [:, :, 2, 1] = - 0.7953173824165425 1.5777370947794298 - -0.7590878884333594 -2.5611758473973656 + 0.7052446292917701 0.13108842508596832 + 1.0763158822603631 0.6872313750939721 [:, :, 1, 2] = - -1.6498806094385716 -0.7620485214729158 - 0.9483351761251866 0.2549002077014988 + 1.9526236951991849 -1.2251108086547753 + -0.31230556977136376 0.4228789490293145 [:, :, 2, 2] = - 0.49178674873737493 0.40293571563213376 - -1.3529106255189678 -0.24471801133340185 + 1.758956134281513 0.478406607325248 + -0.8291284240108506 -0.27121283603553525 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](1)): [:, :, 1, 1] = - -1.9992741558132205 -0.5819769557967861 -1.166558271896703 - 0.09494495069522985 1.3586428445371066 -0.9062964302038041 - -1.2353798677900132 0.6047142666913015 0.4291982129235433 + -0.4669495011047735 0.6805596734309743 0.207265192174864 + -0.1540761201641003 -1.4895675275837277 -1.2670744352013432 + 0.6848941304507589 0.6107677661819959 -0.6424643988051849 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](1)): [:, :, 1, 1] = - 0.05561677705132244 -0.6954946503371727 - -1.1179806484318546 1.1739115957896453 + -0.39230917444809693 1.384675867353184 + 0.025141872885448037 1.6172711762201342 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](0)): [:, :, 1, 1] = - -0.2295229867918132 0.9995826880267514 -0.4464464178107797 - 0.47094459219405244 -0.21362351037135882 -0.384744006062589 + -0.6160083391987968 -0.8011009024001349 0.49161004206242054 + -1.1827569225297236 0.5156815005445983 -0.41243575875482785 [:, :, 1, 2] = - -0.021089375861242016 0.33058252888271933 -0.22527492442602323 - 0.7509905835414588 -0.1023828401983854 -0.5924161307064849 + -1.0452995068792106 1.3614027805565634 1.7467868262625856 + -1.114694177419295 0.2746501650003775 -0.6859354555600069 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](0)): [:, :, 1, 1] = - 1.290907723108442 0.6546580727132467 - 0.20378992978269472 0.8093068830458747 - 1.3552964777049372 -0.4990171549729614 + 1.4249514236204683 2.1225193503220887 + -0.26815955808572195 1.6178076821529923 + 2.0606144899205883 0.6031630372535207 [:, :, 1, 2] = - 1.2610317874466699 1.5451119812004772 - -1.3152478817541067 -0.9104860603715373 - -0.6553463956257289 0.2980016152357741 + 1.1217576394450215 0.32926860078314707 + -1.3168627717860826 -1.4856176906695397 + 1.0267128292431311 0.1544114039787842 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](1)): [:, :, 1, 1] = - 0.4404668363258898 -0.01623448999730994 -0.6003310260708072 - 0.5540220614919812 0.05606215038412198 -0.28566716339443277 + -0.668050856475208 0.37799706709622555 -0.6428199103193484 + 0.01428642671441989 1.4956345615823028 -0.2693828460736291 [:, :, 2, 1] = - -0.4481188070199893 1.1091062993876275 -0.5917669855813718 - 0.11890896619068925 0.7262665678637877 -0.4300984281773213 + -0.5953911693192276 1.2393012457766481 0.4421762382352775 + -1.0068823718566724 -1.46776107271131 0.8253573890739144 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](1)): [:, :, 1, 1] = - 1.126972995377598 -0.7073501696120135 - 0.38393757354364944 -0.06802900763140912 - -1.1566100026255222 1.8093893900382065 + -0.09416181690329534 -0.02824011410596584 + 0.5657198516967806 -1.2207282076601171 + -0.375757769265137 1.1250751780321893 [:, :, 2, 1] = - -0.6711523685328464 -2.2671505336756432 - -0.1392681090236986 -1.0626693009429005 - 1.176524250486563 -0.21043814221544094
    julia> (array = convert(Array, t)) |> disp5×5×3×3 Array{Float64, 4}: + -0.2492924509732966 0.6213507105556024 + 0.2313613167893099 -1.243063945395419 + 0.530248693427408 -0.8094909626726086
    julia> (array = convert(Array, t)) |> disp5×5×3×3 Array{Float64, 4}: [:, :, 1, 1] = - 0.791 1.091 -0.5 0.0 0.0 - 0.393 -0.857 0.171 0.0 0.0 - -0.502 1.022 -0.796 0.0 0.0 - 0.0 0.0 0.0 -0.322 1.223 - 0.0 0.0 0.0 0.289 -0.462 + -0.624 -0.445 0.271 0.0 0.0 + -0.391 -0.141 1.492 0.0 0.0 + 0.293 -0.035 -0.608 0.0 0.0 + 0.0 0.0 0.0 0.725 -0.115 + 0.0 0.0 0.0 -0.722 1.21 [:, :, 2, 1] = - 1.578 0.106 1.526 0.0 0.0 - 2.455 -2.25 0.992 0.0 0.0 - 0.612 -0.361 0.575 0.0 0.0 - 0.0 0.0 0.0 0.795 1.577 - 0.0 0.0 0.0 -0.759 -2.561 + 0.477 -0.197 -2.81 0.0 0.0 + -1.07 -0.731 -1.226 0.0 0.0 + -0.763 2.026 -0.779 0.0 0.0 + 0.0 0.0 0.0 0.705 0.131 + 0.0 0.0 0.0 1.076 0.687 [:, :, 3, 1] = - 0.0 0.0 0.0 1.29 0.654 - 0.0 0.0 0.0 0.203 0.809 - 0.0 0.0 0.0 1.355 -0.499 - -0.229 0.999 -0.446 0.0 0.0 - 0.47 -0.213 -0.384 0.0 0.0 + 0.0 0.0 0.0 1.424 2.122 + 0.0 0.0 0.0 -0.268 1.617 + 0.0 0.0 0.0 2.06 0.603 + -0.616 -0.801 0.491 0.0 0.0 + -1.182 0.515 -0.412 0.0 0.0 [:, :, 1, 2] = - 0.125 -0.103 0.218 0.0 0.0 - 1.076 -1.748 -0.188 0.0 0.0 - -1.025 0.924 -0.298 0.0 0.0 - 0.0 0.0 0.0 -1.649 -0.762 - 0.0 0.0 0.0 0.948 0.254 + -0.433 0.94 0.682 0.0 0.0 + 0.409 -0.411 1.084 0.0 0.0 + -1.308 -1.22 0.123 0.0 0.0 + 0.0 0.0 0.0 1.952 -1.225 + 0.0 0.0 0.0 -0.312 0.422 [:, :, 2, 2] = - -1.399 0.632 -0.539 0.0 0.0 - -1.184 0.407 0.273 0.0 0.0 - 1.566 0.802 0.996 0.0 0.0 - 0.0 0.0 0.0 0.491 0.402 - 0.0 0.0 0.0 -1.352 -0.244 + -0.87 -0.677 0.18 0.0 0.0 + -1.378 -0.564 -0.016 0.0 0.0 + -0.608 -0.159 -1.348 0.0 0.0 + 0.0 0.0 0.0 1.758 0.478 + 0.0 0.0 0.0 -0.829 -0.271 [:, :, 3, 2] = - 0.0 0.0 0.0 1.261 1.545 - 0.0 0.0 0.0 -1.315 -0.91 - 0.0 0.0 0.0 -0.655 0.298 - -0.021 0.33 -0.225 0.0 0.0 - 0.75 -0.102 -0.592 0.0 0.0 + 0.0 0.0 0.0 1.121 0.329 + 0.0 0.0 0.0 -1.316 -1.485 + 0.0 0.0 0.0 1.026 0.154 + -1.045 1.361 1.746 0.0 0.0 + -1.114 0.274 -0.685 0.0 0.0 [:, :, 1, 3] = - 0.0 0.0 0.0 1.126 -0.707 - 0.0 0.0 0.0 0.383 -0.068 - 0.0 0.0 0.0 -1.156 1.809 - 0.44 -0.016 -0.6 0.0 0.0 - 0.554 0.056 -0.285 0.0 0.0 + 0.0 0.0 0.0 -0.094 -0.028 + 0.0 0.0 0.0 0.565 -1.22 + 0.0 0.0 0.0 -0.375 1.125 + -0.668 0.377 -0.642 0.0 0.0 + 0.014 1.495 -0.269 0.0 0.0 [:, :, 2, 3] = - 0.0 0.0 0.0 -0.671 -2.267 - 0.0 0.0 0.0 -0.139 -1.062 - 0.0 0.0 0.0 1.176 -0.21 - -0.448 1.109 -0.591 0.0 0.0 - 0.118 0.726 -0.43 0.0 0.0 + 0.0 0.0 0.0 -0.249 0.621 + 0.0 0.0 0.0 0.231 -1.243 + 0.0 0.0 0.0 0.53 -0.809 + -0.595 1.239 0.442 0.0 0.0 + -1.006 -1.467 0.825 0.0 0.0 [:, :, 3, 3] = - -1.999 -0.581 -1.166 0.0 0.0 - 0.094 1.358 -0.906 0.0 0.0 - -1.235 0.604 0.429 0.0 0.0 - 0.0 0.0 0.0 0.055 -0.695 - 0.0 0.0 0.0 -1.117 1.173
    julia> d1 = dim(codomain(t))25
    julia> d2 = dim(domain(t))9
    julia> (matrix = reshape(array, d1, d2)) |> disp25×9 Array{Float64, 2}: - 0.791 1.578 0.0 0.125 -1.399 0.0 0.0 0.0 -1.999 - 0.393 2.455 0.0 1.076 -1.184 0.0 0.0 0.0 0.094 - -0.502 0.612 0.0 -1.025 1.566 0.0 0.0 0.0 -1.235 - 0.0 0.0 -0.229 0.0 0.0 -0.021 0.44 -0.448 0.0 - 0.0 0.0 0.47 0.0 0.0 0.75 0.554 0.118 0.0 - 1.091 0.106 0.0 -0.103 0.632 0.0 0.0 0.0 -0.581 - -0.857 -2.25 0.0 -1.748 0.407 0.0 0.0 0.0 1.358 - 1.022 -0.361 0.0 0.924 0.802 0.0 0.0 0.0 0.604 - 0.0 0.0 0.999 0.0 0.0 0.33 -0.016 1.109 0.0 - 0.0 0.0 -0.213 0.0 0.0 -0.102 0.056 0.726 0.0 - -0.5 1.526 0.0 0.218 -0.539 0.0 0.0 0.0 -1.166 - 0.171 0.992 0.0 -0.188 0.273 0.0 0.0 0.0 -0.906 - -0.796 0.575 0.0 -0.298 0.996 0.0 0.0 0.0 0.429 - 0.0 0.0 -0.446 0.0 0.0 -0.225 -0.6 -0.591 0.0 - 0.0 0.0 -0.384 0.0 0.0 -0.592 -0.285 -0.43 0.0 - 0.0 0.0 1.29 0.0 0.0 1.261 1.126 -0.671 0.0 - 0.0 0.0 0.203 0.0 0.0 -1.315 0.383 -0.139 0.0 - 0.0 0.0 1.355 0.0 0.0 -0.655 -1.156 1.176 0.0 - -0.322 0.795 0.0 -1.649 0.491 0.0 0.0 0.0 0.055 - 0.289 -0.759 0.0 0.948 -1.352 0.0 0.0 0.0 -1.117 - 0.0 0.0 0.654 0.0 0.0 1.545 -0.707 -2.267 0.0 - 0.0 0.0 0.809 0.0 0.0 -0.91 -0.068 -1.062 0.0 - 0.0 0.0 -0.499 0.0 0.0 0.298 1.809 -0.21 0.0 - 1.223 1.577 0.0 -0.762 0.402 0.0 0.0 0.0 -0.695 - -0.462 -2.561 0.0 0.254 -0.244 0.0 0.0 0.0 1.173
    julia> (u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp25×25 Array{Float64, 2}: + -0.466 0.68 0.207 0.0 0.0 + -0.154 -1.489 -1.267 0.0 0.0 + 0.684 0.61 -0.642 0.0 0.0 + 0.0 0.0 0.0 -0.392 1.384 + 0.0 0.0 0.0 0.025 1.617
    julia> d1 = dim(codomain(t))25
    julia> d2 = dim(domain(t))9
    julia> (matrix = reshape(array, d1, d2)) |> disp25×9 Array{Float64, 2}: + -0.624 0.477 0.0 -0.433 -0.87 0.0 0.0 0.0 -0.466 + -0.391 -1.07 0.0 0.409 -1.378 0.0 0.0 0.0 -0.154 + 0.293 -0.763 0.0 -1.308 -0.608 0.0 0.0 0.0 0.684 + 0.0 0.0 -0.616 0.0 0.0 -1.045 -0.668 -0.595 0.0 + 0.0 0.0 -1.182 0.0 0.0 -1.114 0.014 -1.006 0.0 + -0.445 -0.197 0.0 0.94 -0.677 0.0 0.0 0.0 0.68 + -0.141 -0.731 0.0 -0.411 -0.564 0.0 0.0 0.0 -1.489 + -0.035 2.026 0.0 -1.22 -0.159 0.0 0.0 0.0 0.61 + 0.0 0.0 -0.801 0.0 0.0 1.361 0.377 1.239 0.0 + 0.0 0.0 0.515 0.0 0.0 0.274 1.495 -1.467 0.0 + 0.271 -2.81 0.0 0.682 0.18 0.0 0.0 0.0 0.207 + 1.492 -1.226 0.0 1.084 -0.016 0.0 0.0 0.0 -1.267 + -0.608 -0.779 0.0 0.123 -1.348 0.0 0.0 0.0 -0.642 + 0.0 0.0 0.491 0.0 0.0 1.746 -0.642 0.442 0.0 + 0.0 0.0 -0.412 0.0 0.0 -0.685 -0.269 0.825 0.0 + 0.0 0.0 1.424 0.0 0.0 1.121 -0.094 -0.249 0.0 + 0.0 0.0 -0.268 0.0 0.0 -1.316 0.565 0.231 0.0 + 0.0 0.0 2.06 0.0 0.0 1.026 -0.375 0.53 0.0 + 0.725 0.705 0.0 1.952 1.758 0.0 0.0 0.0 -0.392 + -0.722 1.076 0.0 -0.312 -0.829 0.0 0.0 0.0 0.025 + 0.0 0.0 2.122 0.0 0.0 0.329 -0.028 0.621 0.0 + 0.0 0.0 1.617 0.0 0.0 -1.485 -1.22 -1.243 0.0 + 0.0 0.0 0.603 0.0 0.0 0.154 1.125 -0.809 0.0 + -0.115 0.131 0.0 -1.225 0.478 0.0 0.0 0.0 1.384 + 1.21 0.687 0.0 0.422 -0.271 0.0 0.0 0.0 1.617
    julia> (u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp25×25 Array{Float64, 2}: 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 @@ -370,255 +370,255 @@ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0
    julia> u'*u ≈ I ≈ v'*vtrue
    julia> (u'*matrix*v) |> disp # compare with:25×9 Array{Float64, 2}: - 0.791 1.578 0.125 -1.399 -1.999 0.0 0.0 0.0 0.0 - 0.393 2.455 1.076 -1.184 0.094 0.0 0.0 0.0 0.0 - -0.502 0.612 -1.025 1.566 -1.235 0.0 0.0 0.0 0.0 - 1.091 0.106 -0.103 0.632 -0.581 0.0 0.0 0.0 0.0 - -0.857 -2.25 -1.748 0.407 1.358 0.0 0.0 0.0 0.0 - 1.022 -0.361 0.924 0.802 0.604 0.0 0.0 0.0 0.0 - -0.5 1.526 0.218 -0.539 -1.166 0.0 0.0 0.0 0.0 - 0.171 0.992 -0.188 0.273 -0.906 0.0 0.0 0.0 0.0 - -0.796 0.575 -0.298 0.996 0.429 0.0 0.0 0.0 0.0 - -0.322 0.795 -1.649 0.491 0.055 0.0 0.0 0.0 0.0 - 0.289 -0.759 0.948 -1.352 -1.117 0.0 0.0 0.0 0.0 - 1.223 1.577 -0.762 0.402 -0.695 0.0 0.0 0.0 0.0 - -0.462 -2.561 0.254 -0.244 1.173 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 -0.229 -0.021 0.44 -0.448 - 0.0 0.0 0.0 0.0 0.0 0.47 0.75 0.554 0.118 - 0.0 0.0 0.0 0.0 0.0 0.999 0.33 -0.016 1.109 - 0.0 0.0 0.0 0.0 0.0 -0.213 -0.102 0.056 0.726 - 0.0 0.0 0.0 0.0 0.0 -0.446 -0.225 -0.6 -0.591 - 0.0 0.0 0.0 0.0 0.0 -0.384 -0.592 -0.285 -0.43 - 0.0 0.0 0.0 0.0 0.0 1.29 1.261 1.126 -0.671 - 0.0 0.0 0.0 0.0 0.0 0.203 -1.315 0.383 -0.139 - 0.0 0.0 0.0 0.0 0.0 1.355 -0.655 -1.156 1.176 - 0.0 0.0 0.0 0.0 0.0 0.654 1.545 -0.707 -2.267 - 0.0 0.0 0.0 0.0 0.0 0.809 -0.91 -0.068 -1.062 - 0.0 0.0 0.0 0.0 0.0 -0.499 0.298 1.809 -0.21
    julia> block(t, Z2Irrep(0)) |> disp13×5 Array{Float64, 2}: - 0.791 1.578 0.125 -1.399 -1.999 - 0.393 2.455 1.076 -1.184 0.094 - -0.502 0.612 -1.025 1.566 -1.235 - 1.091 0.106 -0.103 0.632 -0.581 - -0.857 -2.25 -1.748 0.407 1.358 - 1.022 -0.361 0.924 0.802 0.604 - -0.5 1.526 0.218 -0.539 -1.166 - 0.171 0.992 -0.188 0.273 -0.906 - -0.796 0.575 -0.298 0.996 0.429 - -0.322 0.795 -1.649 0.491 0.055 - 0.289 -0.759 0.948 -1.352 -1.117 - 1.223 1.577 -0.762 0.402 -0.695 - -0.462 -2.561 0.254 -0.244 1.173
    julia> block(t, Z2Irrep(1)) |> disp12×4 Array{Float64, 2}: - -0.229 -0.021 0.44 -0.448 - 0.47 0.75 0.554 0.118 - 0.999 0.33 -0.016 1.109 - -0.213 -0.102 0.056 0.726 - -0.446 -0.225 -0.6 -0.591 - -0.384 -0.592 -0.285 -0.43 - 1.29 1.261 1.126 -0.671 - 0.203 -1.315 0.383 -0.139 - 1.355 -0.655 -1.156 1.176 - 0.654 1.545 -0.707 -2.267 - 0.809 -0.91 -0.068 -1.062 - -0.499 0.298 1.809 -0.21

    Here, we illustrated some additional concepts. Firstly, note that we convert a TensorMap to an Array. This only works when sectortype(t) supports fusiontensor, and in particular when BraidingStyle(sectortype(t)) == Bosonic(), e.g. the case of trivial tensors (the category $\mathbf{Vect}$) and group representations (the category $\mathbf{Rep}_{\mathsf{G}}$, which can be interpreted as a subcategory of $\mathbf{Vect}$). Here, we are in this case with $\mathsf{G} = ℤ₂$. For a TensorMap{S,1,1}, the blocks directly correspond to the diagonal blocks in the block diagonal structure of its representation as an Array, there is no basis transform in between. This is no longer the case for TensorMap{S,N₁,N₂} with different values of N₁ and N₂. Here, we use the operation fuse(V), which creates an ElementarySpace which is isomorphic to a given space V (of type ProductSpace or ElementarySpace). The specific map between those two spaces constructed using the specific method unitary implements precisely the basis change from the product basis to the coupled basis. In this case, for a group G with FusionStyle(Irrep[G]) isa UniqueFusion, it is a permutation matrix. Specifically choosing V equal to the codomain and domain of t, we can construct the explicit basis transforms that bring t into block diagonal form.

    Let's repeat the same exercise for I = Irrep[SU₂], which has FusionStyle(I) isa MultipleFusion.

    julia> V1 = SU₂Space(0=>2,1=>1)Rep[SU₂](0=>2, 1=>1)
    julia> V2 = SU₂Space(0=>1,1=>1) + -0.624 0.477 -0.433 -0.87 -0.466 0.0 0.0 0.0 0.0 + -0.391 -1.07 0.409 -1.378 -0.154 0.0 0.0 0.0 0.0 + 0.293 -0.763 -1.308 -0.608 0.684 0.0 0.0 0.0 0.0 + -0.445 -0.197 0.94 -0.677 0.68 0.0 0.0 0.0 0.0 + -0.141 -0.731 -0.411 -0.564 -1.489 0.0 0.0 0.0 0.0 + -0.035 2.026 -1.22 -0.159 0.61 0.0 0.0 0.0 0.0 + 0.271 -2.81 0.682 0.18 0.207 0.0 0.0 0.0 0.0 + 1.492 -1.226 1.084 -0.016 -1.267 0.0 0.0 0.0 0.0 + -0.608 -0.779 0.123 -1.348 -0.642 0.0 0.0 0.0 0.0 + 0.725 0.705 1.952 1.758 -0.392 0.0 0.0 0.0 0.0 + -0.722 1.076 -0.312 -0.829 0.025 0.0 0.0 0.0 0.0 + -0.115 0.131 -1.225 0.478 1.384 0.0 0.0 0.0 0.0 + 1.21 0.687 0.422 -0.271 1.617 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 -0.616 -1.045 -0.668 -0.595 + 0.0 0.0 0.0 0.0 0.0 -1.182 -1.114 0.014 -1.006 + 0.0 0.0 0.0 0.0 0.0 -0.801 1.361 0.377 1.239 + 0.0 0.0 0.0 0.0 0.0 0.515 0.274 1.495 -1.467 + 0.0 0.0 0.0 0.0 0.0 0.491 1.746 -0.642 0.442 + 0.0 0.0 0.0 0.0 0.0 -0.412 -0.685 -0.269 0.825 + 0.0 0.0 0.0 0.0 0.0 1.424 1.121 -0.094 -0.249 + 0.0 0.0 0.0 0.0 0.0 -0.268 -1.316 0.565 0.231 + 0.0 0.0 0.0 0.0 0.0 2.06 1.026 -0.375 0.53 + 0.0 0.0 0.0 0.0 0.0 2.122 0.329 -0.028 0.621 + 0.0 0.0 0.0 0.0 0.0 1.617 -1.485 -1.22 -1.243 + 0.0 0.0 0.0 0.0 0.0 0.603 0.154 1.125 -0.809
    julia> block(t, Z2Irrep(0)) |> disp13×5 Array{Float64, 2}: + -0.624 0.477 -0.433 -0.87 -0.466 + -0.391 -1.07 0.409 -1.378 -0.154 + 0.293 -0.763 -1.308 -0.608 0.684 + -0.445 -0.197 0.94 -0.677 0.68 + -0.141 -0.731 -0.411 -0.564 -1.489 + -0.035 2.026 -1.22 -0.159 0.61 + 0.271 -2.81 0.682 0.18 0.207 + 1.492 -1.226 1.084 -0.016 -1.267 + -0.608 -0.779 0.123 -1.348 -0.642 + 0.725 0.705 1.952 1.758 -0.392 + -0.722 1.076 -0.312 -0.829 0.025 + -0.115 0.131 -1.225 0.478 1.384 + 1.21 0.687 0.422 -0.271 1.617
    julia> block(t, Z2Irrep(1)) |> disp12×4 Array{Float64, 2}: + -0.616 -1.045 -0.668 -0.595 + -1.182 -1.114 0.014 -1.006 + -0.801 1.361 0.377 1.239 + 0.515 0.274 1.495 -1.467 + 0.491 1.746 -0.642 0.442 + -0.412 -0.685 -0.269 0.825 + 1.424 1.121 -0.094 -0.249 + -0.268 -1.316 0.565 0.231 + 2.06 1.026 -0.375 0.53 + 2.122 0.329 -0.028 0.621 + 1.617 -1.485 -1.22 -1.243 + 0.603 0.154 1.125 -0.809

    Here, we illustrated some additional concepts. Firstly, note that we convert a TensorMap to an Array. This only works when sectortype(t) supports fusiontensor, and in particular when BraidingStyle(sectortype(t)) == Bosonic(), e.g. the case of trivial tensors (the category $\mathbf{Vect}$) and group representations (the category $\mathbf{Rep}_{\mathsf{G}}$, which can be interpreted as a subcategory of $\mathbf{Vect}$). Here, we are in this case with $\mathsf{G} = ℤ₂$. For a TensorMap{S,1,1}, the blocks directly correspond to the diagonal blocks in the block diagonal structure of its representation as an Array, there is no basis transform in between. This is no longer the case for TensorMap{S,N₁,N₂} with different values of N₁ and N₂. Here, we use the operation fuse(V), which creates an ElementarySpace which is isomorphic to a given space V (of type ProductSpace or ElementarySpace). The specific map between those two spaces constructed using the specific method unitary implements precisely the basis change from the product basis to the coupled basis. In this case, for a group G with FusionStyle(Irrep[G]) isa UniqueFusion, it is a permutation matrix. Specifically choosing V equal to the codomain and domain of t, we can construct the explicit basis transforms that bring t into block diagonal form.

    Let's repeat the same exercise for I = Irrep[SU₂], which has FusionStyle(I) isa MultipleFusion.

    julia> V1 = SU₂Space(0=>2,1=>1)Rep[SU₂](0=>2, 1=>1)
    julia> V2 = SU₂Space(0=>1,1=>1) # First a `TensorMap{SU₂Space, 1, 1}`Rep[SU₂](0=>1, 1=>1)
    julia> m = randn(V1, V2)TensorMap(Rep[SU₂](0=>2, 1=>1) ← Rep[SU₂](0=>1, 1=>1)): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()): - -0.9099457102487308 - 1.4804865646817287 + -0.5803612706429622 + -1.1923469744947404 * Data for fusiontree FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()): - 0.18373333651075796
    julia> convert(Array, m) |> disp + 1.2898620313358493
    julia> convert(Array, m) |> disp # compare with:5×4 Array{Float64, 2}: - -0.909 0.0 0.0 0.0 - 1.48 0.0 0.0 0.0 - 0.0 0.183 0.0 0.0 - 0.0 0.0 0.183 0.0 - 0.0 0.0 0.0 0.183
    julia> block(m, Irrep[SU₂](0)) |> disp2×1 Array{Float64, 2}: - -0.909 - 1.48
    julia> block(m, Irrep[SU₂](1)) |> disp + -0.58 0.0 0.0 0.0 + -1.192 0.0 0.0 0.0 + 0.0 1.289 0.0 0.0 + 0.0 0.0 1.289 0.0 + 0.0 0.0 0.0 1.289
    julia> block(m, Irrep[SU₂](0)) |> disp2×1 Array{Float64, 2}: + -0.58 + -1.192
    julia> block(m, Irrep[SU₂](1)) |> disp # Now a `TensorMap{SU₂Space, 2, 2}`1×1 Array{Float64, 2}: - 0.183
    julia> t = randn(V1 ⊗ V1, V2 ⊗ V2')TensorMap((Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')): + 1.289
    julia> t = randn(V1 ⊗ V1, V2 ⊗ V2')TensorMap((Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')): * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ()): [:, :, 1, 1] = - -1.6914980604552337 1.1229639990010423 - -0.17718849571178527 -1.2722738353675878 + -0.3874055437351642 -2.099701835523047 + 0.08510553249483822 0.13105443948430304 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ()): [:, :, 1, 1] = - -0.6195449832301526 + -1.9216294987881954 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 0, (false, true), ()): [:, :, 1, 1] = - 0.36658277399958433 0.6554174146215281 - -0.7005803205889816 1.096540759396287 + -0.16698621226133092 -1.0401132934234176 + 2.4053705077096303 0.5453613257612627 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 0, (false, true), ()): [:, :, 1, 1] = - -2.988338073778166 + 2.2422863514054225 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - -0.5100787505738766 -1.1593630130442427 + -0.38712744388045306 -0.398341400065643 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - 0.1059474917261308 - -2.301303493964439 + -1.8459892615784854 + 0.9590043059860077 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - -0.6605652405721436 + 0.1111268679456744 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - -1.2941041726233269 -0.7341448712168943 + 1.2609476435523734 -1.3673952874665583 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - 2.338693397362396 - -0.13356312674521906 + 1.2194372694083306 + -0.22837606130968793 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.25840881651852604 + -1.0494786359755612 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.5805929279424639 -0.5431512168081456 + -0.9396353227085092 1.4764338122393579 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 1.8099991121068406 - -0.6318102385752481 + 0.1450264327248787 + 0.44234346137194674 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.9466624591271173 + -1.1054740984423772 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 2, (false, true), ()): [:, :, 1, 1] = - -0.5592825648305266
    julia> (array = convert(Array, t)) |> disp5×5×4×4 Array{Float64, 4}: + 1.8758979113322243
    julia> (array = convert(Array, t)) |> disp5×5×4×4 Array{Float64, 4}: [:, :, 1, 1] = - -1.691 1.122 0.0 0.0 0.0 - -0.177 -1.272 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.357 - 0.0 0.0 0.0 0.357 0.0 - 0.0 0.0 -0.357 0.0 0.0 + -0.387 -2.099 0.0 0.0 0.0 + 0.085 0.131 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -1.109 + 0.0 0.0 0.0 1.109 0.0 + 0.0 0.0 -1.109 0.0 0.0 [:, :, 2, 1] = - 0.0 0.0 0.105 0.0 0.0 - 0.0 0.0 -2.301 0.0 0.0 - -0.51 -1.159 0.0 -0.467 0.0 - 0.0 0.0 0.467 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -1.845 0.0 0.0 + 0.0 0.0 0.959 0.0 0.0 + -0.387 -0.398 0.0 0.078 0.0 + 0.0 0.0 -0.078 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 [:, :, 3, 1] = - 0.0 0.0 0.0 0.105 0.0 - 0.0 0.0 0.0 -2.301 0.0 - 0.0 0.0 0.0 0.0 -0.467 - -0.51 -1.159 0.0 0.0 0.0 - 0.0 0.0 0.467 0.0 0.0 + 0.0 0.0 0.0 -1.845 0.0 + 0.0 0.0 0.0 0.959 0.0 + 0.0 0.0 0.0 0.0 0.078 + -0.387 -0.398 0.0 0.0 0.0 + 0.0 0.0 -0.078 0.0 0.0 [:, :, 4, 1] = - 0.0 0.0 0.0 0.0 0.105 - 0.0 0.0 0.0 0.0 -2.301 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.467 - -0.51 -1.159 0.0 0.467 0.0 + 0.0 0.0 0.0 0.0 -1.845 + 0.0 0.0 0.0 0.0 0.959 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.078 + -0.387 -0.398 0.0 -0.078 0.0 [:, :, 1, 2] = - 0.0 0.0 0.0 0.0 2.338 - 0.0 0.0 0.0 0.0 -0.133 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.182 - -1.294 -0.734 0.0 -0.182 0.0 + 0.0 0.0 0.0 0.0 1.219 + 0.0 0.0 0.0 0.0 -0.228 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.742 + 1.26 -1.367 0.0 0.742 0.0 [:, :, 2, 2] = - 0.211 0.378 0.0 1.279 0.0 - -0.404 0.633 0.0 -0.446 0.0 - 0.0 0.0 0.0 0.0 -0.615 - 0.41 -0.384 0.0 0.809 0.0 - 0.0 0.0 -1.562 0.0 0.0 + -0.096 -0.6 0.0 0.102 0.0 + 1.388 0.314 0.0 0.312 0.0 + 0.0 0.0 0.0 0.0 0.507 + -0.664 1.043 0.0 -0.122 0.0 + 0.0 0.0 1.612 0.0 0.0 [:, :, 3, 2] = - 0.0 0.0 0.0 0.0 1.279 - 0.0 0.0 0.0 0.0 -0.446 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.193 - 0.41 -0.384 0.0 -0.752 0.0 + 0.0 0.0 0.0 0.0 0.102 + 0.0 0.0 0.0 0.0 0.312 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.385 + -0.664 1.043 0.0 1.49 0.0 [:, :, 4, 2] = - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.559 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 1.875 [:, :, 1, 3] = - 0.0 0.0 0.0 -2.338 0.0 - 0.0 0.0 0.0 0.133 0.0 - 0.0 0.0 0.0 0.0 -0.182 - 1.294 0.734 0.0 0.0 0.0 - 0.0 0.0 0.182 0.0 0.0 + 0.0 0.0 0.0 -1.219 0.0 + 0.0 0.0 0.0 0.228 0.0 + 0.0 0.0 0.0 0.0 0.742 + -1.26 1.367 0.0 0.0 0.0 + 0.0 0.0 -0.742 0.0 0.0 [:, :, 2, 3] = - 0.0 0.0 -1.279 0.0 0.0 - 0.0 0.0 0.446 0.0 0.0 - -0.41 0.384 0.0 -0.193 0.0 - 0.0 0.0 0.752 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.102 0.0 0.0 + 0.0 0.0 -0.312 0.0 0.0 + 0.664 -1.043 0.0 -0.385 0.0 + 0.0 0.0 -1.49 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 [:, :, 3, 3] = - 0.211 0.378 0.0 0.0 0.0 - -0.404 0.633 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.809 - 0.0 0.0 0.0 1.368 0.0 - 0.0 0.0 -0.809 0.0 0.0 + -0.096 -0.6 0.0 0.0 0.0 + 1.388 0.314 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.122 + 0.0 0.0 0.0 -1.998 0.0 + 0.0 0.0 0.122 0.0 0.0 [:, :, 4, 3] = - 0.0 0.0 0.0 0.0 1.279 - 0.0 0.0 0.0 0.0 -0.446 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.752 - 0.41 -0.384 0.0 -0.193 0.0 + 0.0 0.0 0.0 0.0 0.102 + 0.0 0.0 0.0 0.0 0.312 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -1.49 + -0.664 1.043 0.0 -0.385 0.0 [:, :, 1, 4] = - 0.0 0.0 2.338 0.0 0.0 - 0.0 0.0 -0.133 0.0 0.0 - -1.294 -0.734 0.0 0.182 0.0 - 0.0 0.0 -0.182 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 1.219 0.0 0.0 + 0.0 0.0 -0.228 0.0 0.0 + 1.26 -1.367 0.0 -0.742 0.0 + 0.0 0.0 0.742 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 [:, :, 2, 4] = - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 -0.559 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 1.875 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 [:, :, 3, 4] = - 0.0 0.0 -1.279 0.0 0.0 - 0.0 0.0 0.446 0.0 0.0 - -0.41 0.384 0.0 -0.752 0.0 - 0.0 0.0 0.193 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.102 0.0 0.0 + 0.0 0.0 -0.312 0.0 0.0 + 0.664 -1.043 0.0 1.49 0.0 + 0.0 0.0 0.385 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 [:, :, 4, 4] = - 0.211 0.378 0.0 -1.279 0.0 - -0.404 0.633 0.0 0.446 0.0 - 0.0 0.0 0.0 0.0 -1.562 - -0.41 0.384 0.0 0.809 0.0 - 0.0 0.0 -0.615 0.0 0.0
    julia> d1 = dim(codomain(t))25
    julia> d2 = dim(domain(t))16
    julia> (matrix = reshape(array, d1, d2)) |> disp25×16 Array{Float64, 2}: - -1.691 0.0 0.0 0.0 0.0 0.211 0.0 0.0 0.0 0.0 0.211 0.0 0.0 0.0 0.0 0.211 - -0.177 0.0 0.0 0.0 0.0 -0.404 0.0 0.0 0.0 0.0 -0.404 0.0 0.0 0.0 0.0 -0.404 - 0.0 -0.51 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.41 0.0 0.0 -1.294 0.0 -0.41 0.0 - 0.0 0.0 -0.51 0.0 0.0 0.41 0.0 0.0 1.294 0.0 0.0 0.0 0.0 0.0 0.0 -0.41 - 0.0 0.0 0.0 -0.51 -1.294 0.0 0.41 0.0 0.0 0.0 0.0 0.41 0.0 0.0 0.0 0.0 - 1.122 0.0 0.0 0.0 0.0 0.378 0.0 0.0 0.0 0.0 0.378 0.0 0.0 0.0 0.0 0.378 - -1.272 0.0 0.0 0.0 0.0 0.633 0.0 0.0 0.0 0.0 0.633 0.0 0.0 0.0 0.0 0.633 - 0.0 -1.159 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.384 0.0 0.0 -0.734 0.0 0.384 0.0 - 0.0 0.0 -1.159 0.0 0.0 -0.384 0.0 0.0 0.734 0.0 0.0 0.0 0.0 0.0 0.0 0.384 - 0.0 0.0 0.0 -1.159 -0.734 0.0 -0.384 0.0 0.0 0.0 0.0 -0.384 0.0 0.0 0.0 0.0 - 0.0 0.105 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -1.279 0.0 0.0 2.338 0.0 -1.279 0.0 - 0.0 -2.301 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.446 0.0 0.0 -0.133 0.0 0.446 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559 0.0 0.0 - 0.0 0.467 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.752 0.0 0.0 -0.182 0.0 0.193 0.0 - -0.357 0.0 0.467 0.0 0.0 -1.562 0.0 0.0 0.182 0.0 -0.809 0.0 0.0 0.0 0.0 -0.615 - 0.0 0.0 0.105 0.0 0.0 1.279 0.0 0.0 -2.338 0.0 0.0 0.0 0.0 0.0 0.0 -1.279 - 0.0 0.0 -2.301 0.0 0.0 -0.446 0.0 0.0 0.133 0.0 0.0 0.0 0.0 0.0 0.0 0.446 - 0.0 -0.467 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.193 0.0 0.0 0.182 0.0 -0.752 0.0 - 0.357 0.0 0.0 0.0 0.0 0.809 0.0 0.0 0.0 0.0 1.368 0.0 0.0 0.0 0.0 0.809 - 0.0 0.0 0.0 0.467 -0.182 0.0 -0.752 0.0 0.0 0.0 0.0 -0.193 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.105 2.338 0.0 1.279 0.0 0.0 0.0 0.0 1.279 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -2.301 -0.133 0.0 -0.446 0.0 0.0 0.0 0.0 -0.446 0.0 0.0 0.0 0.0 - -0.357 0.0 -0.467 0.0 0.0 -0.615 0.0 0.0 -0.182 0.0 -0.809 0.0 0.0 0.0 0.0 -1.562 - 0.0 0.0 0.0 -0.467 0.182 0.0 0.193 0.0 0.0 0.0 0.0 0.752 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
    julia> (u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp25×25 Array{Float64, 2}: + -0.096 -0.6 0.0 -0.102 0.0 + 1.388 0.314 0.0 -0.312 0.0 + 0.0 0.0 0.0 0.0 1.612 + 0.664 -1.043 0.0 -0.122 0.0 + 0.0 0.0 0.507 0.0 0.0
    julia> d1 = dim(codomain(t))25
    julia> d2 = dim(domain(t))16
    julia> (matrix = reshape(array, d1, d2)) |> disp25×16 Array{Float64, 2}: + -0.387 0.0 0.0 0.0 0.0 -0.096 0.0 0.0 0.0 0.0 -0.096 0.0 0.0 0.0 0.0 -0.096 + 0.085 0.0 0.0 0.0 0.0 1.388 0.0 0.0 0.0 0.0 1.388 0.0 0.0 0.0 0.0 1.388 + 0.0 -0.387 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.664 0.0 0.0 1.26 0.0 0.664 0.0 + 0.0 0.0 -0.387 0.0 0.0 -0.664 0.0 0.0 -1.26 0.0 0.0 0.0 0.0 0.0 0.0 0.664 + 0.0 0.0 0.0 -0.387 1.26 0.0 -0.664 0.0 0.0 0.0 0.0 -0.664 0.0 0.0 0.0 0.0 + -2.099 0.0 0.0 0.0 0.0 -0.6 0.0 0.0 0.0 0.0 -0.6 0.0 0.0 0.0 0.0 -0.6 + 0.131 0.0 0.0 0.0 0.0 0.314 0.0 0.0 0.0 0.0 0.314 0.0 0.0 0.0 0.0 0.314 + 0.0 -0.398 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -1.043 0.0 0.0 -1.367 0.0 -1.043 0.0 + 0.0 0.0 -0.398 0.0 0.0 1.043 0.0 0.0 1.367 0.0 0.0 0.0 0.0 0.0 0.0 -1.043 + 0.0 0.0 0.0 -0.398 -1.367 0.0 1.043 0.0 0.0 0.0 0.0 1.043 0.0 0.0 0.0 0.0 + 0.0 -1.845 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.102 0.0 0.0 1.219 0.0 -0.102 0.0 + 0.0 0.959 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.312 0.0 0.0 -0.228 0.0 -0.312 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.875 0.0 0.0 + 0.0 -0.078 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -1.49 0.0 0.0 0.742 0.0 0.385 0.0 + -1.109 0.0 -0.078 0.0 0.0 1.612 0.0 0.0 -0.742 0.0 0.122 0.0 0.0 0.0 0.0 0.507 + 0.0 0.0 -1.845 0.0 0.0 0.102 0.0 0.0 -1.219 0.0 0.0 0.0 0.0 0.0 0.0 -0.102 + 0.0 0.0 0.959 0.0 0.0 0.312 0.0 0.0 0.228 0.0 0.0 0.0 0.0 0.0 0.0 -0.312 + 0.0 0.078 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.385 0.0 0.0 -0.742 0.0 1.49 0.0 + 1.109 0.0 0.0 0.0 0.0 -0.122 0.0 0.0 0.0 0.0 -1.998 0.0 0.0 0.0 0.0 -0.122 + 0.0 0.0 0.0 -0.078 0.742 0.0 1.49 0.0 0.0 0.0 0.0 -0.385 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 -1.845 1.219 0.0 0.102 0.0 0.0 0.0 0.0 0.102 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.959 -0.228 0.0 0.312 0.0 0.0 0.0 0.0 0.312 0.0 0.0 0.0 0.0 + -1.109 0.0 0.078 0.0 0.0 0.507 0.0 0.0 0.742 0.0 0.122 0.0 0.0 0.0 0.0 1.612 + 0.0 0.0 0.0 0.078 -0.742 0.0 0.385 0.0 0.0 0.0 0.0 -1.49 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.875 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
    julia> (u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp25×25 Array{Float64, 2}: 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 @@ -661,98 +661,98 @@ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.707 0.0 0.0 0.0 0.707 0.0 0.0 0.0 0.0 0.577 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.707 0.0 0.0 0.0 0.408 0.0 0.0
    julia> u'*u ≈ I ≈ v'*vtrue
    julia> (u'*matrix*v) |> disp # compare with:25×16 Array{Float64, 2}: - -1.691 0.366 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 - -0.177 -0.7 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 - 1.122 0.655 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 -0.0 0.0 0.0 - -1.272 1.096 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 - -0.619 -2.988 0.0 -0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 -0.51 0.0 0.0 -1.294 0.0 0.0 0.58 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -0.51 0.0 0.0 -1.294 0.0 0.0 0.58 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.51 0.0 0.0 -1.294 0.0 0.0 0.58 0.0 0.0 0.0 -0.0 0.0 - 0.0 0.0 -1.159 0.0 0.0 -0.734 0.0 0.0 -0.543 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -1.159 0.0 0.0 -0.734 0.0 0.0 -0.543 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -1.159 0.0 0.0 -0.734 0.0 0.0 -0.543 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.105 0.0 0.0 2.338 0.0 0.0 1.809 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.105 0.0 0.0 2.338 0.0 0.0 1.809 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.105 0.0 0.0 2.338 0.0 0.0 1.809 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 -2.301 0.0 0.0 -0.133 0.0 0.0 -0.631 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -2.301 0.0 0.0 -0.133 0.0 0.0 -0.631 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -2.301 0.0 0.0 -0.133 0.0 0.0 -0.631 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 -0.66 0.0 0.0 0.258 0.0 0.0 0.946 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 - -0.0 -0.0 0.0 -0.66 0.0 0.0 0.258 0.0 0.0 0.946 0.0 0.0 0.0 -0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.66 0.0 0.0 0.258 0.0 0.0 0.946 0.0 0.0 0.0 -0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559 0.0 0.0 0.0 0.0 - 0.0 0.0 -0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559 0.0 0.0 0.0 - 0.0 -0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 -0.0 0.0 0.0 0.0 -0.559 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.559
    julia> block(t, SU2Irrep(0)) |> disp5×2 Array{Float64, 2}: - -1.691 0.366 - -0.177 -0.7 - 1.122 0.655 - -1.272 1.096 - -0.619 -2.988
    julia> block(t, SU2Irrep(1)) |> disp5×3 Array{Float64, 2}: - -0.51 -1.294 0.58 - -1.159 -0.734 -0.543 - 0.105 2.338 1.809 - -2.301 -0.133 -0.631 - -0.66 0.258 0.946
    julia> block(t, SU2Irrep(2)) |> disp1×1 Array{Float64, 2}: - -0.559

    Note that the basis transforms u and v are no longer permutation matrices, but are still unitary. Furthermore, note that they render the tensor block diagonal, but that now every element of the diagonal blocks labeled by c comes itself in a tensor product with an identity matrix of size dim(c), i.e. dim(SU2Irrep(1)) = 3 and dim(SU2Irrep(2)) = 5.

    Tensor properties

    Given a t::AbstractTensorMap{S,N₁,N₂}, there are various methods to query its properties. The most important are clearly codomain(t) and domain(t). For t::AbstractTensor{S,N}, i.e. t::AbstractTensorMap{S,N,0}, we can use space(t) as synonym for codomain(t). However, for a general AbstractTensorMap this has no meaning. However, we can query space(t, i), the space associated with the ith index. For i ∈ 1:N₁, this corresponds to codomain(t, i) = codomain(t)[i]. For j = i-N₁ ∈ (1:N₂), this corresponds to dual(domain(t, j)) = dual(domain(t)[j]).

    The total number of indices, i.e. N₁+N₂, is given by numind(t), with N₁ == numout(t) and N₂ == numin(t), the number of outgoing and incoming indices. There are also the unexported methods TensorKit.codomainind(t) and TensorKit.domainind(t) which return the tuples (1, 2, …, N₁) and (N₁+1, …, N₁+N₂), and are useful for internal purposes. The type parameter S<:ElementarySpace can be obtained as spacetype(t); the corresponding sector can directly obtained as sectortype(t) and is Trivial when S != GradedSpace. The underlying field scalars of S can also directly be obtained as field(t). This is different from eltype(t), which returns the type of Number in the tensor data, i.e. the type parameter T in the (subtype of) DenseMatrix{T} in which the matrix blocks are stored. Note that during construction, a (one-time) warning is printed if !(T ⊂ field(S)). The specific DenseMatrix{T} subtype in which the tensor data is stored is obtained as storagetype(t). Each of the methods numind, numout, numin, TensorKit.codomainind, TensorKit.domainind, spacetype, sectortype, field, eltype and storagetype work in the type domain as well, i.e. they are encoded in typeof(t).

    Finally, there are methods to probe the data, which we already encountered. blocksectors(t) returns an iterator over the different coupled sectors that can be obtained from fusing the uncoupled sectors available in the domain, but they must also be obtained from fusing the uncoupled sectors available in the codomain (i.e. it is the intersection of both blocksectors(codomain(t)) and blocksectors(domain(t))). For a specific sector c ∈ blocksectors(t), block(t, c) returns the corresponding data. Both are obtained together with blocks(t), which returns an iterator over the pairs c=>block(t, c). Furthermore, there is fusiontrees(t) which returns an iterator over splitting-fusion tree pairs (f₁,f₂), for which the corresponding data is given by t[f₁,f₂] (i.e. using Base.getindex).

    Let's again illustrate these methods with an example, continuing with the tensor t from the previous example

    julia> typeof(t)TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}
    julia> codomain(t)(Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1))
    julia> domain(t)(Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')
    julia> space(t,1)Rep[SU₂](0=>2, 1=>1)
    julia> space(t,2)Rep[SU₂](0=>2, 1=>1)
    julia> space(t,3)Rep[SU₂](0=>1, 1=>1)'
    julia> space(t,4)Rep[SU₂](0=>1, 1=>1)
    julia> numind(t)4
    julia> numout(t)2
    julia> numin(t)2
    julia> spacetype(t)GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}
    julia> sectortype(t)SU2Irrep
    julia> field(t)
    julia> eltype(t)Float64
    julia> storagetype(t)Vector{Float64} (alias for Array{Float64, 1})
    julia> blocksectors(t)3-element Vector{SU2Irrep}: + -0.387 -0.166 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 -0.0 0.0 0.0 + 0.085 2.405 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 + -2.099 -1.04 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.131 0.545 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 + -1.921 2.242 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 -0.0 0.0 0.0 + 0.0 0.0 -0.387 0.0 0.0 1.26 0.0 0.0 -0.939 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 -0.0 0.0 -0.387 0.0 0.0 1.26 0.0 0.0 -0.939 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.387 0.0 0.0 1.26 0.0 0.0 -0.939 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.398 0.0 0.0 -1.367 0.0 0.0 1.476 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 -0.398 0.0 0.0 -1.367 0.0 0.0 1.476 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.398 0.0 0.0 -1.367 0.0 0.0 1.476 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -1.845 0.0 0.0 1.219 0.0 0.0 0.145 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 + 0.0 -0.0 0.0 -1.845 0.0 0.0 1.219 0.0 0.0 0.145 0.0 0.0 0.0 -0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -1.845 0.0 0.0 1.219 0.0 0.0 0.145 0.0 0.0 0.0 -0.0 0.0 + 0.0 0.0 0.959 0.0 0.0 -0.228 0.0 0.0 0.442 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.959 0.0 0.0 -0.228 0.0 0.0 0.442 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.959 0.0 0.0 -0.228 0.0 0.0 0.442 0.0 0.0 0.0 -0.0 0.0 + 0.0 0.0 0.111 0.0 0.0 -1.049 0.0 0.0 -1.105 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 + -0.0 -0.0 0.0 0.111 0.0 0.0 -1.049 0.0 0.0 -1.105 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.111 0.0 0.0 -1.049 0.0 0.0 -1.105 0.0 0.0 0.0 -0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.875 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 1.875 0.0 0.0 0.0 + -0.0 -0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.875 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 0.0 0.0 -0.0 0.0 0.0 0.0 1.875 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.875
    julia> block(t, SU2Irrep(0)) |> disp5×2 Array{Float64, 2}: + -0.387 -0.166 + 0.085 2.405 + -2.099 -1.04 + 0.131 0.545 + -1.921 2.242
    julia> block(t, SU2Irrep(1)) |> disp5×3 Array{Float64, 2}: + -0.387 1.26 -0.939 + -0.398 -1.367 1.476 + -1.845 1.219 0.145 + 0.959 -0.228 0.442 + 0.111 -1.049 -1.105
    julia> block(t, SU2Irrep(2)) |> disp1×1 Array{Float64, 2}: + 1.875

    Note that the basis transforms u and v are no longer permutation matrices, but are still unitary. Furthermore, note that they render the tensor block diagonal, but that now every element of the diagonal blocks labeled by c comes itself in a tensor product with an identity matrix of size dim(c), i.e. dim(SU2Irrep(1)) = 3 and dim(SU2Irrep(2)) = 5.

    Tensor properties

    Given a t::AbstractTensorMap{S,N₁,N₂}, there are various methods to query its properties. The most important are clearly codomain(t) and domain(t). For t::AbstractTensor{S,N}, i.e. t::AbstractTensorMap{S,N,0}, we can use space(t) as synonym for codomain(t). However, for a general AbstractTensorMap this has no meaning. However, we can query space(t, i), the space associated with the ith index. For i ∈ 1:N₁, this corresponds to codomain(t, i) = codomain(t)[i]. For j = i-N₁ ∈ (1:N₂), this corresponds to dual(domain(t, j)) = dual(domain(t)[j]).

    The total number of indices, i.e. N₁+N₂, is given by numind(t), with N₁ == numout(t) and N₂ == numin(t), the number of outgoing and incoming indices. There are also the unexported methods TensorKit.codomainind(t) and TensorKit.domainind(t) which return the tuples (1, 2, …, N₁) and (N₁+1, …, N₁+N₂), and are useful for internal purposes. The type parameter S<:ElementarySpace can be obtained as spacetype(t); the corresponding sector can directly obtained as sectortype(t) and is Trivial when S != GradedSpace. The underlying field scalars of S can also directly be obtained as field(t). This is different from eltype(t), which returns the type of Number in the tensor data, i.e. the type parameter T in the (subtype of) DenseMatrix{T} in which the matrix blocks are stored. Note that during construction, a (one-time) warning is printed if !(T ⊂ field(S)). The specific DenseMatrix{T} subtype in which the tensor data is stored is obtained as storagetype(t). Each of the methods numind, numout, numin, TensorKit.codomainind, TensorKit.domainind, spacetype, sectortype, field, eltype and storagetype work in the type domain as well, i.e. they are encoded in typeof(t).

    Finally, there are methods to probe the data, which we already encountered. blocksectors(t) returns an iterator over the different coupled sectors that can be obtained from fusing the uncoupled sectors available in the domain, but they must also be obtained from fusing the uncoupled sectors available in the codomain (i.e. it is the intersection of both blocksectors(codomain(t)) and blocksectors(domain(t))). For a specific sector c ∈ blocksectors(t), block(t, c) returns the corresponding data. Both are obtained together with blocks(t), which returns an iterator over the pairs c=>block(t, c). Furthermore, there is fusiontrees(t) which returns an iterator over splitting-fusion tree pairs (f₁,f₂), for which the corresponding data is given by t[f₁,f₂] (i.e. using Base.getindex).

    Let's again illustrate these methods with an example, continuing with the tensor t from the previous example

    julia> typeof(t)TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}
    julia> codomain(t)(Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1))
    julia> domain(t)(Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')
    julia> space(t,1)Rep[SU₂](0=>2, 1=>1)
    julia> space(t,2)Rep[SU₂](0=>2, 1=>1)
    julia> space(t,3)Rep[SU₂](0=>1, 1=>1)'
    julia> space(t,4)Rep[SU₂](0=>1, 1=>1)
    julia> numind(t)4
    julia> numout(t)2
    julia> numin(t)2
    julia> spacetype(t)GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}
    julia> sectortype(t)SU2Irrep
    julia> field(t)
    julia> eltype(t)Float64
    julia> storagetype(t)Vector{Float64} (alias for Array{Float64, 1})
    julia> blocksectors(t)3-element Vector{SU2Irrep}: 0 1 - 2
    julia> blocks(t)Base.Generator{TensorKit.SortedVectorDict{SU2Irrep, Tuple{Tuple{Int64, Int64}, UnitRange{Int64}}}, TensorKit.var"#163#164"{TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}}}(TensorKit.var"#163#164"{TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}}(TensorMap((Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')): + 2
    julia> blocks(t)Base.Generator{TensorKit.SortedVectorDict{SU2Irrep, Tuple{Tuple{Int64, Int64}, UnitRange{Int64}}}, TensorKit.var"#165#166"{TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}}}(TensorKit.var"#165#166"{TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2, 2, Vector{Float64}}}(TensorMap((Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')): * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ()): [:, :, 1, 1] = - -1.6914980604552337 1.1229639990010423 - -0.17718849571178527 -1.2722738353675878 + -0.3874055437351642 -2.099701835523047 + 0.08510553249483822 0.13105443948430304 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ()): [:, :, 1, 1] = - -0.6195449832301526 + -1.9216294987881954 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 0, (false, true), ()): [:, :, 1, 1] = - 0.36658277399958433 0.6554174146215281 - -0.7005803205889816 1.096540759396287 + -0.16698621226133092 -1.0401132934234176 + 2.4053705077096303 0.5453613257612627 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 0, (false, true), ()): [:, :, 1, 1] = - -2.988338073778166 + 2.2422863514054225 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - -0.5100787505738766 -1.1593630130442427 + -0.38712744388045306 -0.398341400065643 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - 0.1059474917261308 - -2.301303493964439 + -1.8459892615784854 + 0.9590043059860077 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 0), 1, (false, true), ()): [:, :, 1, 1] = - -0.6605652405721436 + 0.1111268679456744 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - -1.2941041726233269 -0.7341448712168943 + 1.2609476435523734 -1.3673952874665583 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - 2.338693397362396 - -0.13356312674521906 + 1.2194372694083306 + -0.22837606130968793 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.25840881651852604 + -1.0494786359755612 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.5805929279424639 -0.5431512168081456 + -0.9396353227085092 1.4764338122393579 * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 1.8099991121068406 - -0.6318102385752481 + 0.1450264327248787 + 0.44234346137194674 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ()): [:, :, 1, 1] = - 0.9466624591271173 + -1.1054740984423772 * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1, 1), 2, (false, true), ()): [:, :, 1, 1] = - -0.5592825648305266 + 1.8758979113322243 ), TensorKit.SortedVectorDict{SU2Irrep, Tuple{Tuple{Int64, Int64}, UnitRange{Int64}}}(0 => ((5, 2), 1:10), 1 => ((5, 3), 11:25), 2 => ((1, 1), 26:26)))
    julia> block(t, first(blocksectors(t)))5×2 reshape(view(::Vector{Float64}, 1:10), 5, 2) with eltype Float64: - -1.6915 0.366583 - -0.177188 -0.70058 - 1.12296 0.655417 - -1.27227 1.09654 - -0.619545 -2.98834
    julia> fusiontrees(t)14-element Vector{Tuple{FusionTree{SU2Irrep, 2, 0, 1}, FusionTree{SU2Irrep, 2, 0, 1}}}: + -0.387406 -0.166986 + 0.0851055 2.40537 + -2.0997 -1.04011 + 0.131054 0.545361 + -1.92163 2.24229
    julia> fusiontrees(t)14-element Vector{Tuple{FusionTree{SU2Irrep, 2, 0, 1}, FusionTree{SU2Irrep, 2, 0, 1}}}: (FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()), FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ())) (FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()), FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ())) (FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()), FusionTree{Irrep[SU₂]}((1, 1), 0, (false, true), ())) @@ -768,16 +768,16 @@ (FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()), FusionTree{Irrep[SU₂]}((1, 1), 1, (false, true), ())) (FusionTree{Irrep[SU₂]}((1, 1), 2, (false, false), ()), FusionTree{Irrep[SU₂]}((1, 1), 2, (false, true), ()))
    julia> f1, f2 = first(fusiontrees(t))(FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()), FusionTree{Irrep[SU₂]}((0, 0), 0, (false, true), ()))
    julia> t[f1,f2]2×2×1×1 StridedViews.StridedView{Float64, 4, Vector{Float64}, typeof(identity)}: [:, :, 1, 1] = - -1.6915 1.12296 - -0.177188 -1.27227

    Reading and writing tensors: Dict conversion

    There are no custom or dedicated methods for reading, writing or storing TensorMaps, however, there is the possibility to convert a t::AbstractTensorMap into a Dict, simply as convert(Dict, t). The backward conversion convert(TensorMap, dict) will return a tensor that is equal to t, i.e. t == convert(TensorMap, convert(Dict, t)).

    This conversion relies on that the string represenation of objects such as VectorSpace, FusionTree or Sector should be such that it represents valid code to recreate the object. Hence, we store information about the domain and codomain of the tensor, and the sector associated with each data block, as a String obtained with repr. This provides the flexibility to still change the internal structure of such objects, without this breaking the ability to load older data files. The resulting dictionary can then be stored using any of the provided Julia packages such as JLD.jl, JLD2.jl, BSON.jl, JSON.jl, ...

    Vector space and linear algebra operations

    AbstractTensorMap instances t represent linear maps, i.e. homomorphisms in a 𝕜-linear category, just like matrices. To a large extent, they follow the interface of Matrix in Julia's LinearAlgebra standard library. Many methods from LinearAlgebra are (re)exported by TensorKit.jl, and can then us be used without using LinearAlgebra explicitly. In all of the following methods, the implementation acts directly on the underlying matrix blocks (typically using the same method) and never needs to perform any basis transforms.

    In particular, AbstractTensorMap instances can be composed, provided the domain of the first object coincides with the codomain of the second. Composing tensor maps uses the regular multiplication symbol as in t = t1*t2, which is also used for matrix multiplication. TensorKit.jl also supports (and exports) the mutating method mul!(t, t1, t2). We can then also try to invert a tensor map using inv(t), though this can only exist if the domain and codomain are isomorphic, which can e.g. be checked as fuse(codomain(t)) == fuse(domain(t)). If the inverse is composed with another tensor t2, we can use the syntax t1\t2 or t2/t1. However, this syntax also accepts instances t1 whose domain and codomain are not isomorphic, and then amounts to pinv(t1), the Moore-Penrose pseudoinverse. This, however, is only really justified as minimizing the least squares problem if InnerProductStyle(t) <: EuclideanProduct.

    AbstractTensorMap instances behave themselves as vectors (i.e. they are 𝕜-linear) and so they can be multiplied by scalars and, if they live in the same space, i.e. have the same domain and codomain, they can be added to each other. There is also a zero(t), the additive identity, which produces a zero tensor with the same domain and codomain as t. In addition, TensorMap supports basic Julia methods such as fill! and copy!, as well as copy(t) to create a copy with independent data. Aside from basic + and * operations, TensorKit.jl reexports a number of efficient in-place methods from LinearAlgebra, such as axpy! (for y ← α * x + y), axpby! (for y ← α * x + β * y), lmul! and rmul! (for y ← α*y and y ← y*α, which is typically the same) and mul!, which can also be used for out-of-place scalar multiplication y ← α*x.

    For t::AbstractTensorMap{S} where InnerProductStyle(S) <: EuclideanProduct, we can compute norm(t), and for two such instances, the inner product dot(t1, t2), provided t1 and t2 have the same domain and codomain. Furthermore, there is normalize(t) and normalize!(t) to return a scaled version of t with unit norm. These operations should also exist for InnerProductStyle(S) <: HasInnerProduct, but require an interface for defining a custom inner product in these spaces. Currently, there is no concrete subtype of HasInnerProduct that is not an EuclideanProduct. In particular, CartesianSpace, ComplexSpace and GradedSpace all have InnerProductStyle(V) <: EuclideanProduct.

    With tensors that have InnerProductStyle(t) <: EuclideanProduct there is associated an adjoint operation, given by adjoint(t) or simply t', such that domain(t') == codomain(t) and codomain(t') == domain(t). Note that for an instance t::TensorMap{S,N₁,N₂}, t' is simply stored in a wrapper called AdjointTensorMap{S,N₂,N₁}, which is another subtype of AbstractTensorMap. This should be mostly unvisible to the user, as all methods should work for this type as well. It can be hard to reason about the index order of t', i.e. index i of t appears in t' at index position j = TensorKit.adjointtensorindex(t, i), where the latter method is typically not necessary and hence unexported. There is also a plural TensorKit.adjointtensorindices to convert multiple indices at once. Note that, because the adjoint interchanges domain and codomain, we have space(t', j) == space(t, i)'.

    AbstractTensorMap instances can furthermore be tested for exact (t1 == t2) or approximate (t1 ≈ t2) equality, though the latter requires that norm can be computed.

    When tensor map instances are endomorphisms, i.e. they have the same domain and codomain, there is a multiplicative identity which can be obtained as one(t) or one!(t), where the latter overwrites the contents of t. The multiplicative identity on a space V can also be obtained using id(A, V) as discussed above, such that for a general homomorphism t′, we have t′ == id(codomain(t′))*t′ == t′*id(domain(t′)). Returning to the case of endomorphisms t, we can compute the trace via tr(t) and exponentiate them using exp(t), or if the contents of t can be destroyed in the process, exp!(t). Furthermore, there are a number of tensor factorizations for both endomorphisms and general homomorphism that we discuss below.

    Finally, there are a number of operations that also belong in this paragraph because of their analogy to common matrix operations. The tensor product of two TensorMap instances t1 and t2 is obtained as t1 ⊗ t2 and results in a new TensorMap with codomain(t1⊗t2) = codomain(t1) ⊗ codomain(t2) and domain(t1⊗t2) = domain(t1) ⊗ domain(t2). If we have two TensorMap{S,N,1} instances t1 and t2 with the same codomain, we can combine them in a way that is analoguous to hcat, i.e. we stack them such that the new tensor catdomain(t1, t2) has also the same codomain, but has a domain which is domain(t1) ⊕ domain(t2). Similarly, if t1 and t2 are of type TensorMap{S,1,N} and have the same domain, the operation catcodomain(t1, t2) results in a new tensor with the same domain and a codomain given by codomain(t1) ⊕ codomain(t2), which is the analogy of vcat. Note that direct sum only makes sense between ElementarySpace objects, i.e. there is no way to give a tensor product meaning to a direct sum of tensor product spaces.

    Time for some more examples:

    julia> t == t + zero(t) == t*id(domain(t)) == id(codomain(t))*ttrue
    julia> t2 = randn(ComplexF64, codomain(t), domain(t));
    julia> dot(t2, t)-24.81588398398556 + 6.453731295188567im
    julia> tr(t2'*t)-24.815883983985557 + 6.453731295188568im
    julia> dot(t2, t) ≈ dot(t', t2')true
    julia> dot(t2, t2)93.93276088562416 + 0.0im
    julia> norm(t2)^293.93276088562413
    julia> t3 = copy!(similar(t, ComplexF64), t);
    julia> t3 == ttrue
    julia> rmul!(t3, 0.8);
    julia> t3 ≈ 0.8*ttrue
    julia> axpby!(0.5, t2, 1.3im, t3);
    julia> t3 ≈ 0.5 * t2 + 0.8 * 1.3im * ttrue
    julia> t4 = randn(fuse(codomain(t)), codomain(t));
    julia> t5 = TensorMap(undef, fuse(codomain(t)), domain(t));ERROR: MethodError: no method matching TensorMap(::UndefInitializer, ::GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, ::ProductSpace{GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2}) + -0.387406 -2.0997 + 0.0851055 0.131054

    Reading and writing tensors: Dict conversion

    There are no custom or dedicated methods for reading, writing or storing TensorMaps, however, there is the possibility to convert a t::AbstractTensorMap into a Dict, simply as convert(Dict, t). The backward conversion convert(TensorMap, dict) will return a tensor that is equal to t, i.e. t == convert(TensorMap, convert(Dict, t)).

    This conversion relies on that the string represenation of objects such as VectorSpace, FusionTree or Sector should be such that it represents valid code to recreate the object. Hence, we store information about the domain and codomain of the tensor, and the sector associated with each data block, as a String obtained with repr. This provides the flexibility to still change the internal structure of such objects, without this breaking the ability to load older data files. The resulting dictionary can then be stored using any of the provided Julia packages such as JLD.jl, JLD2.jl, BSON.jl, JSON.jl, ...

    Vector space and linear algebra operations

    AbstractTensorMap instances t represent linear maps, i.e. homomorphisms in a 𝕜-linear category, just like matrices. To a large extent, they follow the interface of Matrix in Julia's LinearAlgebra standard library. Many methods from LinearAlgebra are (re)exported by TensorKit.jl, and can then us be used without using LinearAlgebra explicitly. In all of the following methods, the implementation acts directly on the underlying matrix blocks (typically using the same method) and never needs to perform any basis transforms.

    In particular, AbstractTensorMap instances can be composed, provided the domain of the first object coincides with the codomain of the second. Composing tensor maps uses the regular multiplication symbol as in t = t1*t2, which is also used for matrix multiplication. TensorKit.jl also supports (and exports) the mutating method mul!(t, t1, t2). We can then also try to invert a tensor map using inv(t), though this can only exist if the domain and codomain are isomorphic, which can e.g. be checked as fuse(codomain(t)) == fuse(domain(t)). If the inverse is composed with another tensor t2, we can use the syntax t1\t2 or t2/t1. However, this syntax also accepts instances t1 whose domain and codomain are not isomorphic, and then amounts to pinv(t1), the Moore-Penrose pseudoinverse. This, however, is only really justified as minimizing the least squares problem if InnerProductStyle(t) <: EuclideanProduct.

    AbstractTensorMap instances behave themselves as vectors (i.e. they are 𝕜-linear) and so they can be multiplied by scalars and, if they live in the same space, i.e. have the same domain and codomain, they can be added to each other. There is also a zero(t), the additive identity, which produces a zero tensor with the same domain and codomain as t. In addition, TensorMap supports basic Julia methods such as fill! and copy!, as well as copy(t) to create a copy with independent data. Aside from basic + and * operations, TensorKit.jl reexports a number of efficient in-place methods from LinearAlgebra, such as axpy! (for y ← α * x + y), axpby! (for y ← α * x + β * y), lmul! and rmul! (for y ← α*y and y ← y*α, which is typically the same) and mul!, which can also be used for out-of-place scalar multiplication y ← α*x.

    For t::AbstractTensorMap{S} where InnerProductStyle(S) <: EuclideanProduct, we can compute norm(t), and for two such instances, the inner product dot(t1, t2), provided t1 and t2 have the same domain and codomain. Furthermore, there is normalize(t) and normalize!(t) to return a scaled version of t with unit norm. These operations should also exist for InnerProductStyle(S) <: HasInnerProduct, but require an interface for defining a custom inner product in these spaces. Currently, there is no concrete subtype of HasInnerProduct that is not an EuclideanProduct. In particular, CartesianSpace, ComplexSpace and GradedSpace all have InnerProductStyle(V) <: EuclideanProduct.

    With tensors that have InnerProductStyle(t) <: EuclideanProduct there is associated an adjoint operation, given by adjoint(t) or simply t', such that domain(t') == codomain(t) and codomain(t') == domain(t). Note that for an instance t::TensorMap{S,N₁,N₂}, t' is simply stored in a wrapper called AdjointTensorMap{S,N₂,N₁}, which is another subtype of AbstractTensorMap. This should be mostly unvisible to the user, as all methods should work for this type as well. It can be hard to reason about the index order of t', i.e. index i of t appears in t' at index position j = TensorKit.adjointtensorindex(t, i), where the latter method is typically not necessary and hence unexported. There is also a plural TensorKit.adjointtensorindices to convert multiple indices at once. Note that, because the adjoint interchanges domain and codomain, we have space(t', j) == space(t, i)'.

    AbstractTensorMap instances can furthermore be tested for exact (t1 == t2) or approximate (t1 ≈ t2) equality, though the latter requires that norm can be computed.

    When tensor map instances are endomorphisms, i.e. they have the same domain and codomain, there is a multiplicative identity which can be obtained as one(t) or one!(t), where the latter overwrites the contents of t. The multiplicative identity on a space V can also be obtained using id(A, V) as discussed above, such that for a general homomorphism t′, we have t′ == id(codomain(t′))*t′ == t′*id(domain(t′)). Returning to the case of endomorphisms t, we can compute the trace via tr(t) and exponentiate them using exp(t), or if the contents of t can be destroyed in the process, exp!(t). Furthermore, there are a number of tensor factorizations for both endomorphisms and general homomorphism that we discuss below.

    Finally, there are a number of operations that also belong in this paragraph because of their analogy to common matrix operations. The tensor product of two TensorMap instances t1 and t2 is obtained as t1 ⊗ t2 and results in a new TensorMap with codomain(t1⊗t2) = codomain(t1) ⊗ codomain(t2) and domain(t1⊗t2) = domain(t1) ⊗ domain(t2). If we have two TensorMap{S,N,1} instances t1 and t2 with the same codomain, we can combine them in a way that is analoguous to hcat, i.e. we stack them such that the new tensor catdomain(t1, t2) has also the same codomain, but has a domain which is domain(t1) ⊕ domain(t2). Similarly, if t1 and t2 are of type TensorMap{S,1,N} and have the same domain, the operation catcodomain(t1, t2) results in a new tensor with the same domain and a codomain given by codomain(t1) ⊕ codomain(t2), which is the analogy of vcat. Note that direct sum only makes sense between ElementarySpace objects, i.e. there is no way to give a tensor product meaning to a direct sum of tensor product spaces.

    Time for some more examples:

    julia> t == t + zero(t) == t*id(domain(t)) == id(codomain(t))*ttrue
    julia> t2 = randn(ComplexF64, codomain(t), domain(t));
    julia> dot(t2, t)10.540344215995272 + 2.655112057420387im
    julia> tr(t2'*t)10.540344215995272 + 2.6551120574203875im
    julia> dot(t2, t) ≈ dot(t', t2')true
    julia> dot(t2, t2)64.34023912937965 + 0.0im
    julia> norm(t2)^264.34023912937965
    julia> t3 = copy!(similar(t, ComplexF64), t);
    julia> t3 == ttrue
    julia> rmul!(t3, 0.8);
    julia> t3 ≈ 0.8*ttrue
    julia> axpby!(0.5, t2, 1.3im, t3);
    julia> t3 ≈ 0.5 * t2 + 0.8 * 1.3im * ttrue
    julia> t4 = randn(fuse(codomain(t)), codomain(t));
    julia> t5 = TensorMap(undef, fuse(codomain(t)), domain(t));ERROR: MethodError: no method matching TensorMap(::UndefInitializer, ::GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, ::ProductSpace{GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 2}) The type `TensorMap` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: - TensorMap(::typeof(randnormal), ::TensorSpace, ::TensorSpace) + TensorMap(::typeof(randuniform), ::TensorSpace, ::TensorSpace) @ TensorKit deprecated.jl:103 TensorMap(::typeof(randn), ::TensorSpace, ::TensorSpace) @ TensorKit deprecated.jl:103 - TensorMap(::typeof(ones), ::TensorSpace, ::TensorSpace) + TensorMap(::typeof(randhaar), ::TensorSpace, ::TensorSpace) @ TensorKit deprecated.jl:103 ...
    julia> mul!(t5, t4, t) == t4*tERROR: SpaceMismatch("(Rep[SU₂](1/2=>1) ⊗ Rep[SU₂](1/2=>1)) ← (Rep[SU₂](1/2=>1) ⊗ Rep[SU₂](1/2=>1)) ≠ Rep[SU₂](0=>5, 1=>5, 2=>1) ← (Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) * (Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')")
    julia> inv(t4) * t4 ≈ id(codomain(t))true
    julia> t4 * inv(t4) ≈ id(fuse(codomain(t)))true
    julia> t4 \ (t4 * t) ≈ ttrue
    julia> t6 = randn(ComplexF64, V1, codomain(t));
    julia> numout(t4) == numout(t6) == 1true
    julia> t7 = catcodomain(t4, t6);ERROR: MethodError: no method matching catcodomain(::TensorMap{Float64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 1, 2, Vector{Float64}}, ::TensorMap{ComplexF64, GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 1, 2, Vector{ComplexF64}}) The function `catcodomain` exists, but no method is defined for this combination of argument types. @@ -794,31 +794,31 @@ p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})

    and

    permute(t::AbstractTensorMap{S,N₁,N₂},
             p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int}; copy = false)

    both of which return an instance of AbstractTensorMap{S,N₁′,N₂′}.

    In these methods, p1 and p2 specify which of the original tensor indices ranging from 1 to N₁+N₂ make up the new codomain (with N₁′ spaces) and new domain (with N₂′ spaces). Hence, (p1..., p2...) should be a valid permutation of 1:(N₁+N₂). Note that, throughout TensorKit.jl, permutations are always specified using tuples of Ints, for reasons of type stability. For braid, we also need to specify levels or depths for each of the indices of the original tensor, which determine whether indices will braid over or underneath each other (use the braiding or its inverse). We refer to the section on manipulating fusion trees for more details.

    When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can use the simpler interface of permute, which does not require the argument levels. permute accepts a keyword argument copy. When copy == true, the result will be a tensor with newly allocated data that can independently be modified from that of the input tensor t. When copy takes the default value false, permute can try to return the result in a way that it shares its data with the input tensor t, though this is only possible in specific cases (e.g. when sectortype(S) == Trivial and (p1..., p2...) = (1:(N₁+N₂)...)).

    Both braid and permute come in a version where the result is stored in an already existing tensor, i.e. braid!(tdst, tsrc, levels, p1, p2) and permute!(tdst, tsrc, p1, p2).

    Another operation that belongs und index manipulations is taking the transpose of a tensor, i.e. LinearAlgebra.transpose(t) and LinearAlgebra.transpose!(tdst, tsrc), both of which are reexported by TensorKit.jl. Note that transpose(t) is not simply equal to reshuffling domain and codomain with braid(t, (1:(N₁+N₂)...), reverse(domainind(tsrc)), reverse(codomainind(tsrc)))). Indeed, the graphical representation (where we draw the codomain and domain as a single object), makes clear that this introduces an additional (inverse) twist, which is then compensated in the transpose implementation.

    transpose

    In categorical language, the reason for this extra twist is that we use the left coevaluation $η$, but the right evaluation $\tilde{ϵ}$, when repartitioning the indices between domain and codomain.

    There are a number of other index related manipulations. We can apply a twist (or inverse twist) to one of the tensor map indices via twist(t, i; inv = false) or twist!(t, i; inv = false). Note that the latter method does not store the result in a new destination tensor, but just modifies the tensor t in place. Twisting several indices simultaneously can be obtained by using the defining property

    $θ_{V⊗W} = τ_{W,V} ∘ (θ_W ⊗ θ_V) ∘ τ_{V,W} = (θ_V ⊗ θ_W) ∘ τ_{W,V} ∘ τ_{V,W}.$

    but is currently not implemented explicitly.

    For all sector types I with BraidingStyle(I) == Bosonic(), all twists are 1 and thus have no effect. Let us start with some examples, in which we illustrate that, albeit permute might act highly non-trivial on the fusion trees and on the corresponding data, after conversion to a regular Array (when possible), it just acts like permutedims

    julia> domain(t) → codomain(t)(Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1)) ← (Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)')
    julia> ta = convert(Array, t);
    julia> t′ = permute(t, (1,2,3,4));
    julia> domain(t′) → codomain(t′)(Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)' ⊗ Rep[SU₂](0=>1, 1=>1)) ← ProductSpace{GradedSpace{SU2Irrep, TensorKit.SortedVectorDict{SU2Irrep, Int64}}, 0}()
    julia> convert(Array, t′) ≈ tatrue
    julia> t′′ = permute(t, (4,2,3),(1,));
    julia> domain(t′′) → codomain(t′′)(Rep[SU₂](0=>1, 1=>1) ⊗ Rep[SU₂](0=>2, 1=>1) ⊗ Rep[SU₂](0=>1, 1=>1)') ← Rep[SU₂](0=>2, 1=>1)'
    julia> convert(Array, t′′) ≈ permutedims(ta, (4,2,3,1))true
    julia> mTensorMap(Rep[SU₂](0=>2, 1=>1) ← Rep[SU₂](0=>1, 1=>1)): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()): - -0.9099457102487308 - 1.4804865646817287 + -0.5803612706429622 + -1.1923469744947404 * Data for fusiontree FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()): - 0.18373333651075796
    julia> transpose(m)TensorMap(Rep[SU₂](0=>1, 1=>1)' ← Rep[SU₂](0=>2, 1=>1)'): + 1.2898620313358493
    julia> transpose(m)TensorMap(Rep[SU₂](0=>1, 1=>1)' ← Rep[SU₂](0=>2, 1=>1)'): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (true,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (true,), ()): - -0.9099457102487308 1.4804865646817287 + -0.5803612706429622 -1.1923469744947404 * Data for fusiontree FusionTree{Irrep[SU₂]}((1,), 1, (true,), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (true,), ()): - 0.18373333651075793
    julia> convert(Array, transpose(t)) ≈ permutedims(ta,(4,3,2,1))true
    julia> dot(t2, t) ≈ dot(transpose(t2), transpose(t))true
    julia> transpose(transpose(t)) ≈ ttrue
    julia> twist(t, 3) ≈ t + 1.289862031335849
    julia> convert(Array, transpose(t)) ≈ permutedims(ta,(4,3,2,1))true
    julia> dot(t2, t) ≈ dot(transpose(t2), transpose(t))true
    julia> transpose(transpose(t)) ≈ ttrue
    julia> twist(t, 3) ≈ t # as twist acts trivially fortrue
    julia> BraidingStyle(sectortype(t))Bosonic()

    Note that transpose acts like one would expect on a TensorMap{S,1,1}. On a TensorMap{S,N₁,N₂}, because transpose replaces the codomain with the dual of the domain, which has its tensor product operation reversed, this in the end amounts in a complete reversal of all tensor indices when representing it as a plain mutli-dimensional Array. Also, note that we have not defined the conjugation of TensorMap instances. One definition that one could think of is conj(t) = adjoint(transpose(t)). However note that codomain(adjoint(tranpose(t))) == domain(transpose(t)) == dual(codomain(t)) and similarly domain(adjoint(tranpose(t))) == dual(domain(t)), where dual of a ProductSpace is composed of the dual of the ElementarySpace instances, in reverse order of tensor product. This might be very confusing, and as such we leave tensor conjugation undefined. However, note that we have a conjugation syntax within the context of tensor contractions.

    To show the effect of twist, we now consider a type of sector I for which BraidingStyle{I} != Bosonic(). In particular, we use FibonacciAnyon. We cannot convert the resulting TensorMap to an Array, so we have to rely on indirect tests to verify our results.

    julia> V1 = GradedSpace{FibonacciAnyon}(:I=>3,:τ=>2)Vect[FibonacciAnyon](:I=>3, :τ=>2)
    julia> V2 = GradedSpace{FibonacciAnyon}(:I=>2,:τ=>1)Vect[FibonacciAnyon](:I=>2, :τ=>1)
    julia> m = TensorMap(randn, Float32, V1, V2)┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon └ @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/tensors/tensor.jl:35 TensorMap(Vect[FibonacciAnyon](:I=>3, :τ=>2) ← Vect[FibonacciAnyon](:I=>2, :τ=>1)): * Data for fusiontree FusionTree{FibonacciAnyon}((:I,), :I, (false,), ()) ← FusionTree{FibonacciAnyon}((:I,), :I, (false,), ()): - 0.8853426f0 -0.4884279f0 - -0.8940747f0 -0.74551857f0 - 1.3112198f0 -1.5229253f0 + -0.030267442f0 0.22753693f0 + 0.123218514f0 -1.7719132f0 + 0.3468305f0 -1.0303339f0 * Data for fusiontree FusionTree{FibonacciAnyon}((:τ,), :τ, (false,), ()) ← FusionTree{FibonacciAnyon}((:τ,), :τ, (false,), ()): - -0.44375545f0 - 1.3958972f0
    julia> transpose(m)┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon + -0.33852103f0 + 1.251154f0
    julia> transpose(m)┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon └ @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/tensors/tensor.jl:35 TensorMap(Vect[FibonacciAnyon](:I=>2, :τ=>1)' ← Vect[FibonacciAnyon](:I=>3, :τ=>2)'): * Data for fusiontree FusionTree{FibonacciAnyon}((:I,), :I, (true,), ()) ← FusionTree{FibonacciAnyon}((:I,), :I, (true,), ()): - 0.8853426f0 -0.8940747f0 1.3112198f0 - -0.4884279f0 -0.74551857f0 -1.5229253f0 + -0.030267442f0 0.123218514f0 0.3468305f0 + 0.22753693f0 -1.7719132f0 -1.0303339f0 * Data for fusiontree FusionTree{FibonacciAnyon}((:τ,), :τ, (true,), ()) ← FusionTree{FibonacciAnyon}((:τ,), :τ, (true,), ()): - -0.44375545f0 1.3958972f0
    julia> twist(braid(m, (1,2), (2,), (1,)), 1)ERROR: AssertionError: length(levels) == numind(t)
    julia> t1 = randn(V1*V2', V2*V1);┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon + -0.33852103f0 1.251154f0
    julia> twist(braid(m, (1,2), (2,), (1,)), 1)ERROR: AssertionError: length(levels) == numind(t)
    julia> t1 = randn(V1*V2', V2*V1);┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon └ @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/tensors/tensor.jl:35
    julia> t2 = randn(ComplexF64, V1*V2', V2*V1);
    julia> dot(t1, t2) ≈ dot(transpose(t1), transpose(t2))┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon └ @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/tensors/tensor.jl:35 true
    julia> transpose(transpose(t1)) ≈ t1┌ Warning: Tensors with real data might be incompatible with sector type FibonacciAnyon @@ -826,21 +826,21 @@ true

    A final operation that one might expect in this section is to fuse or join indices, and its inverse, to split a given index into two or more indices. For a plain tensor (i.e. with sectortype(t) == Trivial) amount to the equivalent of reshape on the multidimensional data. However, this represents only one possibility, as there is no canonically unique way to embed the tensor product of two spaces V₁ ⊗ V₂ in a new space V = fuse(V₁⊗V₂). Such a mapping can always be accompagnied by a basis transform. However, one particular choice is created by the function isomorphism, or for EuclideanProduct spaces, unitary. Hence, we can join or fuse two indices of a tensor by first constructing u = unitary(fuse(space(t, i) ⊗ space(t, j)), space(t, i) ⊗ space(t, j)) and then contracting this map with indices i and j of t, as explained in the section on contracting tensors. Note, however, that a typical algorithm is not expected to often need to fuse and split indices, as e.g. tensor factorizations can easily be applied without needing to reshape or fuse indices first, as explained in the next section.

    Tensor factorizations

    Eigenvalue decomposition

    As tensors are linear maps, they have various kinds of factorizations. Endomorphism, i.e. tensor maps t with codomain(t) == domain(t), have an eigenvalue decomposition. For this, we overload both LinearAlgebra.eigen(t; kwargs...) and LinearAlgebra.eigen!(t; kwargs...), where the latter destroys t in the process. The keyword arguments are the same that are accepted by LinearAlgebra.eigen(!) for matrices. The result is returned as D, V = eigen(t), such that t*V ≈ V*D. For given t::TensorMap{S,N,N}, V is a TensorMap{S,N,1}, whose codomain corresponds to that of t, but whose domain is a single space S (or more correctly a ProductSpace{S,1}), that corresponds to fuse(codomain(t)). The eigenvalues are encoded in D, a TensorMap{S,1,1}, whose domain and codomain correspond to the domain of V. Indeed, we cannot reasonably associate a tensor product structure with the different eigenvalues. Note that D stores the eigenvalues on the diagonal of a (collection of) DenseMatrix instance(s), as there is currently no dedicated DiagonalTensorMap or diagonal storage support.

    We also define LinearAlgebra.ishermitian(t), which can only return true for instances of AbstractEuclideanTensorMap. In all other cases, as the inner product is not defined, there is no notion of hermiticity (i.e. we are not working in a -category). For instances of EuclideanTensorMap, we also define and export the routines eigh and eigh!, which compute the eigenvalue decomposition under the guarantee (not checked) that the map is hermitian. Hence, eigenvalues will be real and V will be unitary with eltype(V) == eltype(t). We also define and export eig and eig!, which similarly assume that the TensorMap is not hermitian (hence this does not require EuclideanTensorMap), and always returns complex values eigenvalues and eigenvectors. Like for matrices, LinearAlgebra.eigen is type unstable and checks hermiticity at run-time, then falling back to either eig or eigh.

    Orthogonal factorizations

    Other factorizations that are provided by TensorKit.jl are orthogonal or unitary in nature, and thus always require a AbstractEuclideanTensorMap. However, they don't require equal domain and codomain. Let us first discuss the singular value decomposition, for which we define and export the methods tsvd and tsvd! (where as always, the latter destroys the input).

    U, Σ, Vʰ, ϵ = tsvd(t; trunc = notrunc(), p::Real = 2,
                             alg::OrthogonalFactorizationAlgorithm = SDD())

    This computes a (possibly truncated) singular value decomposition of t::TensorMap{S,N₁,N₂} (with InnerProductStyle(t)<:EuclideanProduct), such that norm(t - U*Σ*Vʰ) ≈ ϵ, where U::TensorMap{S,N₁,1}, S::TensorMap{S,1,1}, Vʰ::TensorMap{S,1,N₂} and ϵ::Real. U is an isometry, i.e. U'*U approximates the identity, whereas U*U' is an idempotent (squares to itself). The same holds for adjoint(Vʰ). The domain of U equals the domain and codomain of Σ and the codomain of . In the case of trunc = notrunc() (default value, see below), this space is given by min(fuse(codomain(t)), fuse(domain(t))). The singular values are contained in Σ and are stored on the diagonal of a (collection of) DenseMatrix instance(s), similar to the eigenvalues before.

    The keyword argument trunc provides a way to control the truncation, and is connected to the keyword argument p. The default value notrunc() implies no truncation, and thus ϵ = 0. Other valid options are

    Furthermore, the alg keyword can be either SVD() or SDD() (default), which corresponds to two different algorithms in LAPACK to compute singular value decompositions. The default value SDD() uses a divide-and-conquer algorithm and is typically the fastest, but can loose some accuracy. The SVD() method uses a QR-iteration scheme and can be more accurate, but is typically slower. Since Julia 1.3, these two algorithms are also available in the LinearAlgebra standard library, where they are specified as LinearAlgebra.DivideAndConquer() and LinearAlgebra.QRIteration().

    Note that we defined the new method tsvd (truncated or tensor singular value decomposition), rather than overloading LinearAlgebra.svd. We (will) also support LinearAlgebra.svd(t) as alternative for tsvd(t; trunc = notrunc()), but note that the return values are then given by U, Σ, V = svd(t) with V = adjoint(Vʰ).

    We also define the following pair of orthogonal factorization algorithms, which are useful when one is not interested in truncating a tensor or knowing the singular values, but only in its image or coimage.

    Furthermore, we can compute an orthonormal basis for the orthogonal complement of the image and of the co-image (i.e. the kernel) with the following methods:

    Note that the methods leftorth, rightorth, leftnull and rightnull also come in a form with exclamation mark, i.e. leftorth!, rightorth!, leftnull! and rightnull!, which destroy the input tensor t.

    Factorizations for custom index bipartions

    Finally, note that each of the factorizations take a single argument, the tensor map t, and a number of keyword arguments. They perform the factorization according to the given codomain and domain of the tensor map. In many cases, we want to perform the factorization according to a different bipartition of the indices. When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can immediately specify an alternative bipartition of the indices of t in all of these methods, in the form

    factorize(t::AbstracTensorMap, pleft::NTuple{N₁′,Int}, pright::NTuple{N₂′,Int}; kwargs...)

    where pleft will be the indices in the codomain of the new tensor map, and pright the indices of the domain. Here, factorize is any of the methods LinearAlgebra.eigen, eig, eigh, tsvd, LinearAlgebra.svd, leftorth, rightorth, leftnull and rightnull. This signature does not allow for the exclamation mark, because it amounts to

    factorize!(permute(t, pleft, pright; copy = true); kwargs...)

    where permute was introduced and discussed in the previous section. When the braiding is not symmetric, the user should manually apply braid to bring the tensor map in proper form before performing the factorization.

    Some examples to conclude this section

    julia> V1 = SU₂Space(0=>2,1/2=>1)Rep[SU₂](0=>2, 1/2=>1)
    julia> V2 = SU₂Space(0=>1,1/2=>1,1=>1)Rep[SU₂](0=>1, 1/2=>1, 1=>1)
    julia> t = randn(V1 ⊗ V1, V2);
    julia> U, S, W = tsvd(t);
    julia> t ≈ U * S * Wtrue
    julia> D, V = eigh(t'*t);
    julia> D ≈ S*Strue
    julia> U'*U ≈ id(domain(U))true
    julia> STensorMap(Rep[SU₂](0=>1, 1/2=>1, 1=>1) ← Rep[SU₂](0=>1, 1/2=>1, 1=>1)): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()): - 2.827575856039351 + 3.6436526371146662 * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()): - 2.397586026710947 + 2.383880520872053 * Data for fusiontree FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()): - 0.5563940537911656
    julia> Q, R = leftorth(t; alg = Polar());
    julia> isposdef(R)true
    julia> Q ≈ U*Wtrue
    julia> R ≈ W'*S*Wtrue
    julia> U2, S2, W2, ε = tsvd(t; trunc = truncspace(V1));
    julia> W2*W2' ≈ id(codomain(W2))true
    julia> S2TensorMap(Rep[SU₂](0=>1, 1/2=>1) ← Rep[SU₂](0=>1, 1/2=>1)): + 0.39449563282232586
    julia> Q, R = leftorth(t; alg = Polar());
    julia> isposdef(R)true
    julia> Q ≈ U*Wtrue
    julia> R ≈ W'*S*Wtrue
    julia> U2, S2, W2, ε = tsvd(t; trunc = truncspace(V1));
    julia> W2*W2' ≈ id(codomain(W2))true
    julia> S2TensorMap(Rep[SU₂](0=>1, 1/2=>1) ← Rep[SU₂](0=>1, 1/2=>1)): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()): - 2.827575856039351 + 3.6436526371146662 * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()): - 2.397586026710947
    julia> ε ≈ norm(block(S, Irrep[SU₂](1)))*sqrt(dim(Irrep[SU₂](1)))true
    julia> L, Q = rightorth(t, (1,), (2,3));
    julia> codomain(L), domain(L), domain(Q)(ProductSpace(Rep[SU₂](0=>2, 1/2=>1)), ProductSpace(Rep[SU₂](0=>2, 1/2=>1)), (Rep[SU₂](0=>2, 1/2=>1)' ⊗ Rep[SU₂](0=>1, 1/2=>1, 1=>1)))
    julia> Q*Q'TensorMap(Rep[SU₂](0=>2, 1/2=>1) ← Rep[SU₂](0=>2, 1/2=>1)): + 2.383880520872053
    julia> ε ≈ norm(block(S, Irrep[SU₂](1)))*sqrt(dim(Irrep[SU₂](1)))true
    julia> L, Q = rightorth(t, (1,), (2,3));
    julia> codomain(L), domain(L), domain(Q)(ProductSpace(Rep[SU₂](0=>2, 1/2=>1)), ProductSpace(Rep[SU₂](0=>2, 1/2=>1)), (Rep[SU₂](0=>2, 1/2=>1)' ⊗ Rep[SU₂](0=>1, 1/2=>1, 1=>1)))
    julia> Q*Q'TensorMap(Rep[SU₂](0=>2, 1/2=>1) ← Rep[SU₂](0=>2, 1/2=>1)): * Data for fusiontree FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()): - 0.9999999999999997 -5.43905956410031e-17 - -5.43905956410031e-17 1.0000000000000002 + 1.0000000000000002 -1.0037349933677021e-16 + -1.0037349933677021e-16 1.0 * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()): - 0.9999999999999999
    julia> P = Q'*Q;
    julia> P ≈ P*Ptrue
    julia> t′ = permute(t, (1,), (2,3));
    julia> t′ ≈ t′ * Ptrue

    Bosonic tensor contractions and tensor networks

    One of the most important operation with tensor maps is to compose them, more generally known as contracting them. As mentioned in the section on category theory, a typical composition of maps in a ribbon category can graphically be represented as a planar arrangement of the morphisms (i.e. tensor maps, boxes with lines eminating from top and bottom, corresponding to source and target, i.e. domain and codomain), where the lines connecting the source and targets of the different morphisms should be thought of as ribbons, that can braid over or underneath each other, and that can twist. Technically, we can embed this diagram in $ℝ × [0,1]$ and attach all the unconnected line endings corresponding objects in the source at some position $(x,0)$ for $x∈ℝ$, and all line endings corresponding to objects in the target at some position $(x,1)$. The resulting morphism is then invariant under what is known as framed three-dimensional isotopy, i.e. three-dimensional rearrangements of the morphism that respect the rules of boxes connected by ribbons whose open endings are kept fixed. Such a two-dimensional diagram cannot easily be encoded in a single line of code.

    However, things simplify when the braiding is symmetric (such that over- and under- crossings become equivalent, i.e. just crossings), and when twists, i.e. self-crossings in this case, are trivial. This amounts to BraidingStyle(I) == Bosonic() in the language of TensorKit.jl, and is true for any subcategory of $\mathbf{Vect}$, i.e. ordinary tensors, possibly with some symmetry constraint. The case of $\mathbf{SVect}$ and its subcategories, and more general categories, are discussed below.

    In the case of triival twists, we can deform the diagram such that we first combine every morphism with a number of coevaluations $η$ so as to represent it as a tensor, i.e. with a trivial domain. We can then rearrange the morphism to be all ligned up horizontally, where the original morphism compositions are now being performed by evaluations $ϵ$. This process will generate a number of crossings and twists, where the latter can be omitted because they act trivially. Similarly, double crossings can also be omitted. As a consequence, the diagram, or the morphism it represents, is completely specified by the tensors it is composed of, and which indices between the different tensors are connect, via the evaluation $ϵ$, and which indices make up the source and target of the resulting morphism. If we also compose the resulting morphisms with coevaluations so that it has a trivial domain, we just have one type of unconnected lines, henceforth called open indices. We sketch such a rearrangement in the following picture

    tensor unitary

    Hence, we can now specify such a tensor diagram, henceforth called a tensor contraction or also tensor network, using a one-dimensional syntax that mimicks abstract index notation and specifies which indices are connected by the evaluation map using Einstein's summation conventation. Indeed, for BraidingStyle(I) == Bosonic(), such a tensor contraction can take the same format as if all tensors were just multi-dimensional arrays. For this, we rely on the interface provided by the package TensorOperations.jl.

    The above picture would be encoded as

    @tensor E[a,b,c,d,e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    or

    @tensor E[:] := A[1,2,-4,3]*B[4,5,-3,3]*C[1,-5,4,-2]*D[-1,2,5]

    where the latter syntax is known as NCON-style, and labels the unconnected or outgoing indices with negative integers, and the contracted indices with positive integers.

    A number of remarks are in order. TensorOperations.jl accepts both integers and any valid variable name as dummy label for indices, and everything in between [ ] is not resolved in the current context but interpreted as a dummy label. Here, we label the indices of a TensorMap, like A::TensorMap{S,N₁,N₂}, in a linear fashion, where the first position corresponds to the first space in codomain(A), and so forth, up to position N₁. Index N₁+1then corresponds to the first space in domain(A). However, because we have applied the coevaluation $η$, it actually corresponds to the corresponding dual space, in accordance with the interface of space(A, i) that we introduced above, and as indiated by the dotted box around $A$ in the above picture. The same holds for the other tensor maps. Note that our convention also requires that we braid indices that we brought from the domain to the codomain, and so this is only unambiguous for a symmetric braiding, where there is a unique way to permute the indices.

    With the current syntax, we create a new object E because we use the definition operator :=. Furthermore, with the current syntax, it will be a Tensor, i.e. it will have a trivial domain, and correspond to the dotted box in the picture above, rather than the actual morphism E. We can also directly define E with the correct codomain and domain by rather using

    @tensor E[a b c;d e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    or

    @tensor E[(a,b,c);(d,e)] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    where the latter syntax can also be used when the codomain is empty. When using the assignment operator =, the TensorMap E is assumed to exist and the contents will be written to the currently allocated memory. Note that for existing tensors, both on the left hand side and right hand side, trying to specify the indices in the domain and the codomain seperately using the above syntax, has no effect, as the bipartition of indices are already fixed by the existing object. Hence, if E has been created by the previous line of code, all of the following lines are now equivalent

    @tensor E[(a,b,c);(d,e)] = A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]
    + 1.0
    julia> P = Q'*Q;
    julia> P ≈ P*Ptrue
    julia> t′ = permute(t, (1,), (2,3));
    julia> t′ ≈ t′ * Ptrue

    Bosonic tensor contractions and tensor networks

    One of the most important operation with tensor maps is to compose them, more generally known as contracting them. As mentioned in the section on category theory, a typical composition of maps in a ribbon category can graphically be represented as a planar arrangement of the morphisms (i.e. tensor maps, boxes with lines eminating from top and bottom, corresponding to source and target, i.e. domain and codomain), where the lines connecting the source and targets of the different morphisms should be thought of as ribbons, that can braid over or underneath each other, and that can twist. Technically, we can embed this diagram in $ℝ × [0,1]$ and attach all the unconnected line endings corresponding objects in the source at some position $(x,0)$ for $x∈ℝ$, and all line endings corresponding to objects in the target at some position $(x,1)$. The resulting morphism is then invariant under what is known as framed three-dimensional isotopy, i.e. three-dimensional rearrangements of the morphism that respect the rules of boxes connected by ribbons whose open endings are kept fixed. Such a two-dimensional diagram cannot easily be encoded in a single line of code.

    However, things simplify when the braiding is symmetric (such that over- and under- crossings become equivalent, i.e. just crossings), and when twists, i.e. self-crossings in this case, are trivial. This amounts to BraidingStyle(I) == Bosonic() in the language of TensorKit.jl, and is true for any subcategory of $\mathbf{Vect}$, i.e. ordinary tensors, possibly with some symmetry constraint. The case of $\mathbf{SVect}$ and its subcategories, and more general categories, are discussed below.

    In the case of triival twists, we can deform the diagram such that we first combine every morphism with a number of coevaluations $η$ so as to represent it as a tensor, i.e. with a trivial domain. We can then rearrange the morphism to be all ligned up horizontally, where the original morphism compositions are now being performed by evaluations $ϵ$. This process will generate a number of crossings and twists, where the latter can be omitted because they act trivially. Similarly, double crossings can also be omitted. As a consequence, the diagram, or the morphism it represents, is completely specified by the tensors it is composed of, and which indices between the different tensors are connect, via the evaluation $ϵ$, and which indices make up the source and target of the resulting morphism. If we also compose the resulting morphisms with coevaluations so that it has a trivial domain, we just have one type of unconnected lines, henceforth called open indices. We sketch such a rearrangement in the following picture

    tensor unitary

    Hence, we can now specify such a tensor diagram, henceforth called a tensor contraction or also tensor network, using a one-dimensional syntax that mimicks abstract index notation and specifies which indices are connected by the evaluation map using Einstein's summation conventation. Indeed, for BraidingStyle(I) == Bosonic(), such a tensor contraction can take the same format as if all tensors were just multi-dimensional arrays. For this, we rely on the interface provided by the package TensorOperations.jl.

    The above picture would be encoded as

    @tensor E[a,b,c,d,e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    or

    @tensor E[:] := A[1,2,-4,3]*B[4,5,-3,3]*C[1,-5,4,-2]*D[-1,2,5]

    where the latter syntax is known as NCON-style, and labels the unconnected or outgoing indices with negative integers, and the contracted indices with positive integers.

    A number of remarks are in order. TensorOperations.jl accepts both integers and any valid variable name as dummy label for indices, and everything in between [ ] is not resolved in the current context but interpreted as a dummy label. Here, we label the indices of a TensorMap, like A::TensorMap{S,N₁,N₂}, in a linear fashion, where the first position corresponds to the first space in codomain(A), and so forth, up to position N₁. Index N₁+1then corresponds to the first space in domain(A). However, because we have applied the coevaluation $η$, it actually corresponds to the corresponding dual space, in accordance with the interface of space(A, i) that we introduced above, and as indiated by the dotted box around $A$ in the above picture. The same holds for the other tensor maps. Note that our convention also requires that we braid indices that we brought from the domain to the codomain, and so this is only unambiguous for a symmetric braiding, where there is a unique way to permute the indices.

    With the current syntax, we create a new object E because we use the definition operator :=. Furthermore, with the current syntax, it will be a Tensor, i.e. it will have a trivial domain, and correspond to the dotted box in the picture above, rather than the actual morphism E. We can also directly define E with the correct codomain and domain by rather using

    @tensor E[a b c;d e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    or

    @tensor E[(a,b,c);(d,e)] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]

    where the latter syntax can also be used when the codomain is empty. When using the assignment operator =, the TensorMap E is assumed to exist and the contents will be written to the currently allocated memory. Note that for existing tensors, both on the left hand side and right hand side, trying to specify the indices in the domain and the codomain seperately using the above syntax, has no effect, as the bipartition of indices are already fixed by the existing object. Hence, if E has been created by the previous line of code, all of the following lines are now equivalent

    @tensor E[(a,b,c);(d,e)] = A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]
     @tensor E[a,b,c,d,e] = A[v w d;x]*B[(y,z,c);(x,)]*C[v e y; b]*D[a,w,z]
     @tensor E[a b; c d e] = A[v; w d x]*B[y,z,c,x]*C[v,e,y,b]*D[a w;z]

    and none of those will or can change the partition of the indices of E into its codomain and its domain.

    Two final remarks are in order. Firstly, the order of the tensors appearing on the right hand side is irrelevant, as we can reorder them by using the allowed moves of the Penrose graphical calculus, which yields some crossings and a twist. As the latter is trivial, it can be omitted, and we just use the same rules to evaluate the newly ordered tensor network. For the particular case of matrix matrix multiplication, which also captures more general settings by appropriotely combining spaces into a single line, we indeed find

    tensor contraction reorder

    or thus, the following to lines of code yield the same result

    @tensor C[i,j] := B[i,k]*A[k,j]
     @tensor C[i,j] := A[k,j]*B[i,k]

    Reordering of tensors can be used internally by the @tensor macro to evaluate the contraction in a more efficient manner. In particular, the NCON-style of specifying the contraction gives the user control over the order, and there are other macros, such as @tensoropt, that try to automate this process. There is also an @ncon macro and ncon function, an we recommend reading the manual of TensorOperations.jl to learn more about the possibilities and how they work.

    A final remark involves the use of adjoints of tensors. The current framework is such that the user should not be to worried about the actual bipartition into codomain and domain of a given TensorMap instance. Indeed, for factorizations one just specifies the requested bipartition via the factorize(t, pleft, pright) interface, and for tensor contractions the @contract macro figures out the correct manipulations automatically. However, when wanting to use the adjoint of an instance t::TensorMap{S,N₁,N₂}, the resulting adjoint(t) is a AbstractTensorMap{S,N₂,N₁} and one need to know the values of N₁ and N₂ to know exactly where the ith index of t will end up in adjoint(t), and hence to know and understand the index order of t'. Within the @tensor macro, one can instead use conj() on the whole index expression so as to be able to use the original index ordering of t. Indeed, for matrices of thus, TensorMap{S,1,1} instances, this yields exactly the equivalence one expects, namely equivalence between the following to expressions.

    @tensor C[i,j] := B'[i,k]*A[k,j]
    -@tensor C[i,j] := conj(B[k,i])*A[k,j]

    For e.g. an instance A::TensorMap{S,3,2}, the following two syntaxes have the same effect within an @tensor expression: conj(A[a,b,c,d,e]) and A'[d,e,a,b,c].

    Some examples:

    Fermionic tensor contractions

    TODO

    Anyonic tensor contractions

    TODO

    +@tensor C[i,j] := conj(B[k,i])*A[k,j]

    For e.g. an instance A::TensorMap{S,3,2}, the following two syntaxes have the same effect within an @tensor expression: conj(A[a,b,c,d,e]) and A'[d,e,a,b,c].

    Some examples:

    Fermionic tensor contractions

    TODO

    Anyonic tensor contractions

    TODO

    diff --git a/previews/PR187/man/tutorial/index.html b/previews/PR187/man/tutorial/index.html index a610ac07..95265be1 100644 --- a/previews/PR187/man/tutorial/index.html +++ b/previews/PR187/man/tutorial/index.html @@ -1,652 +1,652 @@ Tutorial · TensorKit.jl

    Tutorial

    Before discussing at length all aspects of this package, both its usage and implementation, we start with a short tutorial to sketch the main capabilities. Thereto, we start by loading TensorKit.jl

    julia> using TensorKit

    Cartesian tensors

    The most important objects in TensorKit.jl are tensors, which we now create with random (normally distributed) entries in the following manner

    julia> A = randn(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)TensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()):
     [:, :, 1] =
    - -1.7713227962791482  -1.6654441588823108
    - -0.3661364928846668   0.4201119694338522
    -  0.6249181393710261   0.11839611010215755
    +  1.559698370904021    -0.6538732046390204
    +  0.09602644522157724  -0.5853568628515657
    + -0.9118228769692547    1.186197821583879
     
     [:, :, 2] =
    - -1.1342284367830684   -0.01620275659939573
    -  0.08988953356351839  -1.683406193198217
    - -0.7633896348297693   -1.9354511137307029
    + 0.6209246273656582  -0.9488784917570874
    + 1.174100995600476    1.3569245936609886
    + 0.8147431107294355   0.4623455513149724
     
     [:, :, 3] =
    -  0.6428491839818875  -0.7801505609087104
    -  0.7027020258464614  -1.373717569571017
    - -1.42943426105255     0.1945426962157891
    + -0.42964284585426216   0.6816367293169704
    + -0.12782295103392152   0.29669669759580464
    +  0.25432347923136284  -1.0516095155825909
     
     [:, :, 4] =
    - -0.6339286229742425  -0.713617786902105
    - -0.5043010175077616   0.9663915579392787
    - -0.7660241120537823  -0.9979288631362535

    Note that we entered the tensor size not as plain dimensions, by specifying the vector space associated with these tensor indices, in this case ℝ^n, which can be obtained by typing \bbR+TAB. The tensor then lives in the tensor product of the different spaces, which we can obtain by typing (i.e. \otimes+TAB), although for simplicity also the usual multiplication sign * does the job. Note also that A is printed as an instance of a parametric type TensorMap, which we will discuss below and contains Tensor.

    Let us briefly sidetrack into the nature of ℝ^n:

    julia> V = ℝ^3ℝ^3
    julia> typeof(V)CartesianSpace
    julia> V == CartesianSpace(3)true
    julia> supertype(CartesianSpace)ElementarySpace
    julia> supertype(ElementarySpace)VectorSpace

    i.e. ℝ^n can also be created without Unicode using the longer syntax CartesianSpace(n). It is subtype of ElementarySpace, with a standard (Euclidean) inner product over the real numbers. Furthermore,

    julia> W = ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> typeof(W)ProductSpace{CartesianSpace, 3}
    julia> supertype(ProductSpace)CompositeSpace
    julia> supertype(CompositeSpace)VectorSpace

    i.e. the tensor product of a number of CartesianSpaces is some generic parametric ProductSpace type, specifically ProductSpace{CartesianSpace,N} for the tensor product of N instances of CartesianSpace.

    Tensors are itself vectors (but not Vectors or even AbstractArrays), so we can compute linear combinations, provided they live in the same space.

    julia> B = randn(ℝ^3 * ℝ^2 * ℝ^4);
    julia> C = 0.5*A + 2.5*BTensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()): + 0.700722084783788 0.28289696338599857 + -0.6773560414856941 1.0413143134178862 + -0.5147053588941117 0.8258844807657844

    Note that we entered the tensor size not as plain dimensions, by specifying the vector space associated with these tensor indices, in this case ℝ^n, which can be obtained by typing \bbR+TAB. The tensor then lives in the tensor product of the different spaces, which we can obtain by typing (i.e. \otimes+TAB), although for simplicity also the usual multiplication sign * does the job. Note also that A is printed as an instance of a parametric type TensorMap, which we will discuss below and contains Tensor.

    Let us briefly sidetrack into the nature of ℝ^n:

    julia> V = ℝ^3ℝ^3
    julia> typeof(V)CartesianSpace
    julia> V == CartesianSpace(3)true
    julia> supertype(CartesianSpace)ElementarySpace
    julia> supertype(ElementarySpace)VectorSpace

    i.e. ℝ^n can also be created without Unicode using the longer syntax CartesianSpace(n). It is subtype of ElementarySpace, with a standard (Euclidean) inner product over the real numbers. Furthermore,

    julia> W = ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> typeof(W)ProductSpace{CartesianSpace, 3}
    julia> supertype(ProductSpace)CompositeSpace
    julia> supertype(CompositeSpace)VectorSpace

    i.e. the tensor product of a number of CartesianSpaces is some generic parametric ProductSpace type, specifically ProductSpace{CartesianSpace,N} for the tensor product of N instances of CartesianSpace.

    Tensors are itself vectors (but not Vectors or even AbstractArrays), so we can compute linear combinations, provided they live in the same space.

    julia> B = randn(ℝ^3 * ℝ^2 * ℝ^4);
    julia> C = 0.5*A + 2.5*BTensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()): [:, :, 1] = - -2.953298667027734 -0.7529780674371294 - -2.930806683501682 -1.1475877575805542 - 0.1146596381558867 -0.09405501809281072 + 3.0171082373371485 -0.6793135862414672 + 2.122522252462985 -2.3465952051446965 + -0.6617514272383918 -0.41895421358338636 [:, :, 2] = - -0.07714113151600022 2.174356682217938 - 3.3341047013788234 -4.981025231980809 - -1.906672967497598 1.531576295079438 + -0.6711718398531372 1.2267527128370492 + -0.177368513208771 1.651824913580295 + -0.601728916082851 -2.1633463140634728 [:, :, 3] = - -5.559123232893744 1.4594373783087033 - 1.8829457358647255 2.418790313813882 - -1.6777452422168384 3.8876073999849763 + -2.235977010851955 0.7940586225176662 + -2.0085174364537552 -4.737485185198689 + -1.4156298507026581 -0.14650643013430065 [:, :, 4] = - -6.498112065121697 -1.0084545197612855 - 3.462391552749877 0.9716454939251764 - 1.2882270808625402 1.1001676866978256

    Given that they are behave as vectors, they also have a scalar product and norm, which they inherit from the Euclidean inner product on the individual ℝ^n spaces:

    julia> scalarBA = dot(B,A)0.2520372122777289
    julia> scalarAA = dot(A,A)24.249699173821128
    julia> normA² = norm(A)^224.249699173821128

    More generally, our tensor objects implement the full interface layed out in VectorInterface.jl.

    If two tensors live on different spaces, these operations have no meaning and are thus not allowed

    julia> B′ = randn(ℝ^4 * ℝ^2 * ℝ^3);
    julia> space(B′) == space(A)false
    julia> C′ = 0.5*A + 2.5*B′ERROR: SpaceMismatch("(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}() ≠ (ℝ^4 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()")
    julia> scalarBA′ = dot(B′,A)ERROR: SpaceMismatch("(ℝ^4 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}() ≠ (ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()")

    However, in this particular case, we can reorder the indices of B′ to match space of A, using the routine permute (we deliberately choose not to overload permutedims from Julia Base, for reasons that become clear below):

    julia> space(permute(B′,(3,2,1))) == space(A)true

    We can contract two tensors using Einstein summation convention, which takes the interface from TensorOperations.jl. TensorKit.jl reexports the @tensor macro

    julia> @tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]TensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()):
    +  3.8918321464058034   1.2107269998276582
    + -0.6606102533568612   0.05099658658778172
    + -0.0957859379464954  -2.385479808988701

    Given that they are behave as vectors, they also have a scalar product and norm, which they inherit from the Euclidean inner product on the individual ℝ^n spaces:

    julia> scalarBA = dot(B,A)-0.1269626216839731
    julia> scalarAA = dot(A,A)15.819152041534121
    julia> normA² = norm(A)^215.819152041534121

    More generally, our tensor objects implement the full interface layed out in VectorInterface.jl.

    If two tensors live on different spaces, these operations have no meaning and are thus not allowed

    julia> B′ = randn(ℝ^4 * ℝ^2 * ℝ^3);
    julia> space(B′) == space(A)false
    julia> C′ = 0.5*A + 2.5*B′ERROR: SpaceMismatch("(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}() ≠ (ℝ^4 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()")
    julia> scalarBA′ = dot(B′,A)ERROR: SpaceMismatch("(ℝ^4 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}() ≠ (ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()")

    However, in this particular case, we can reorder the indices of B′ to match space of A, using the routine permute (we deliberately choose not to overload permutedims from Julia Base, for reasons that become clear below):

    julia> space(permute(B′,(3,2,1))) == space(A)true

    We can contract two tensors using Einstein summation convention, which takes the interface from TensorOperations.jl. TensorKit.jl reexports the @tensor macro

    julia> @tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]TensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^2 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()):
     [:, :, 1, 1] =
    -  1.2979250934774638   4.973714064795268
    - -0.08561314307339178  0.16453598568190994
    -  4.5898481373061095   1.5324835820611475
    +  2.4919562125752948  -0.36290186957933557
    + -1.2312753517973054   0.1786063591970532
    + -2.0706420503436482   2.9001160409516
     
     [:, :, 2, 1] =
    - -0.405841545060391   -0.45841738567628726
    -  0.7181059154969496  -2.724365947223309
    - -1.504325152264236   -1.2817977378886845
    + 0.42449832539011384  -0.3089494958004667
    + 0.472526756686103     1.5050341627429311
    + 0.5088984318086058    0.3100073956297056
     
     [:, :, 1, 2] =
    - -0.09347884614677669   0.27090948193096787
    -  0.20188629617642528  -2.082250600448651
    - -3.705105979321537    -4.040087188311053
    +  1.3483451114599252   -0.8190854428844754
    + -0.09266775157186534  -1.2655111448313094
    + -1.137299449288416     1.5545758643166887
     
     [:, :, 2, 2] =
    -  3.514633567395031   -0.17731680854357576
    -  0.8244085629094323   1.0414188325281546
    - -1.0007944286080468   3.186983671767584
    + -0.33161757430574446  -1.2175323392123534
    +  0.7552979104160599    0.23375160151421367
    +  0.6660013667590451    1.1055127731133048
     
     [:, :, 1, 3] =
    -  0.16060482599810383  -0.034874518035410376
    - -0.6336749546859356    2.168823913526477
    -  0.4547679846947424    0.42918964737371307
    + -0.06862435038953982   0.03447612535207099
    + -0.44671439405829133  -0.6153131226180035
    + -0.4439981756212004    0.41805188851783354
     
     [:, :, 2, 3] =
    - -0.45617590169626654  -1.553385225673065
    -  0.8551221953409989   -3.1732748466144467
    - -3.4586886911258343   -2.285543608922769
    julia> @tensor d = A[a,b,c]*A[a,b,c]24.249699173821128
    julia> d ≈ scalarAA ≈ normA²true

    We hope that the index convention is clear. The := is to create a new tensor D, without the : the result would be written in an existing tensor D, which in this case would yield an error as no tensor D exists. If the contraction yields a scalar, regular assignment with = can be used.

    Finally, we can factorize a tensor, creating a bipartition of a subset of its indices and its complement. With a plain Julia Array, one would apply permutedims and reshape to cast the array into a matrix before applying e.g. the singular value decomposition. With TensorKit.jl, one just specifies which indices go to the left (rows) and right (columns)

    julia> U, S, Vd = tsvd(A, (1,3), (2,));
    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A ≈ A′true
    julia> UTensorMap((ℝ^3 ⊗ ℝ^4) ← ℝ^2): + -2.0756769713297434 0.9602962613992274 + -0.4246184243718396 -2.1833079898740158 + 0.2034890221074913 -2.007055012779949
    julia> @tensor d = A[a,b,c]*A[a,b,c]15.819152041534121
    julia> d ≈ scalarAA ≈ normA²true

    We hope that the index convention is clear. The := is to create a new tensor D, without the : the result would be written in an existing tensor D, which in this case would yield an error as no tensor D exists. If the contraction yields a scalar, regular assignment with = can be used.

    Finally, we can factorize a tensor, creating a bipartition of a subset of its indices and its complement. With a plain Julia Array, one would apply permutedims and reshape to cast the array into a matrix before applying e.g. the singular value decomposition. With TensorKit.jl, one just specifies which indices go to the left (rows) and right (columns)

    julia> U, S, Vd = tsvd(A, (1,3), (2,));
    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A ≈ A′true
    julia> UTensorMap((ℝ^3 ⊗ ℝ^4) ← ℝ^2): [:, :, 1] = - 0.5684806480775036 0.13013557350093358 … 0.2296348947284623 - -0.0527122202757327 0.36485563011835925 -0.15895221985745936 - -0.0960779138567796 0.5161707349827374 0.30768477700841795 + -0.45100617948000493 -0.35233974941424195 … -0.0579131342181588 + -0.1645564755607091 0.12452473938670548 0.3859170017047509 + 0.4654069224211132 -0.03385689103677773 0.30193518119093143 [:, :, 2] = - 0.29383115687167904 0.3531145503291005 … 0.08668828867985 - 0.1807989571372515 -0.2926099639077251 0.30993077425157023 - -0.17735813468483824 -0.0646378099319175 0.08345087396021492

    Note that the tsvd routine returns the decomposition of the linear map as three factors, U, S and Vd, each of them a TensorMap, such that Vd is already what is commonly called V'. Furthermore, observe that U is printed differently then A, i.e. as a TensorMap((ℝ^3 ⊗ ℝ^4) ← ProductSpace(ℝ^2)). What this means is that tensors (or more appropriately, TensorMap instances) in TensorKit.jl are always considered to be linear maps between two ProductSpace instances, with

    julia> codomain(U)(ℝ^3 ⊗ ℝ^4)
    julia> domain(U)ProductSpace(ℝ^2)
    julia> codomain(A)(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> domain(A)ProductSpace{CartesianSpace, 0}()

    An instance of TensorMap thus represents a linear map from its domain to its codomain, making it an element of the space of homomorphisms between these two spaces. That space is represented using its own type HomSpace in TensorKit.jl, and which admits a direct constructor as well as a unicode alternative using the symbol (obtained as \to+TAB or \rightarrow+TAB) or (obtained as \leftarrow+TAB).

    julia> P = space(U)(ℝ^3 ⊗ ℝ^4) ← ℝ^2
    julia> space(U) == HomSpace(ℝ^3 ⊗ ℝ^4, ℝ^2) == (ℝ^3 ⊗ ℝ^4 ← ℝ^2) == ℝ^2 → ℝ^3 ⊗ ℝ^4ERROR: MethodError: no method matching HomSpace(::ProductSpace{CartesianSpace, 2}, ::CartesianSpace) + 0.3718691492607936 -0.025701950042169876 … 0.3125766685504112 + -0.11464904986775987 0.7465247309492882 0.029602004458702976 + -0.014545030058092866 0.39710350429083285 0.03122855264392123

    Note that the tsvd routine returns the decomposition of the linear map as three factors, U, S and Vd, each of them a TensorMap, such that Vd is already what is commonly called V'. Furthermore, observe that U is printed differently then A, i.e. as a TensorMap((ℝ^3 ⊗ ℝ^4) ← ProductSpace(ℝ^2)). What this means is that tensors (or more appropriately, TensorMap instances) in TensorKit.jl are always considered to be linear maps between two ProductSpace instances, with

    julia> codomain(U)(ℝ^3 ⊗ ℝ^4)
    julia> domain(U)ProductSpace(ℝ^2)
    julia> codomain(A)(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> domain(A)ProductSpace{CartesianSpace, 0}()

    An instance of TensorMap thus represents a linear map from its domain to its codomain, making it an element of the space of homomorphisms between these two spaces. That space is represented using its own type HomSpace in TensorKit.jl, and which admits a direct constructor as well as a unicode alternative using the symbol (obtained as \to+TAB or \rightarrow+TAB) or (obtained as \leftarrow+TAB).

    julia> P = space(U)(ℝ^3 ⊗ ℝ^4) ← ℝ^2
    julia> space(U) == HomSpace(ℝ^3 ⊗ ℝ^4, ℝ^2) == (ℝ^3 ⊗ ℝ^4 ← ℝ^2) == ℝ^2 → ℝ^3 ⊗ ℝ^4ERROR: MethodError: no method matching HomSpace(::ProductSpace{CartesianSpace, 2}, ::CartesianSpace) The type `HomSpace` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: HomSpace(::P1, ::P2) where {S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}} @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/spaces/homspace.jl:12
    julia> (codomain(P), domain(P))((ℝ^3 ⊗ ℝ^4), ProductSpace(ℝ^2))

    Furthermore, a Tensor instance such as A is just a specific case of TensorMap with an empty domain, i.e. a ProductSpace{CartesianSpace,0} instance. Analogously, we can represent a vector v and matrix m as

    julia> v = randn(ℝ^3)TensorMap(ℝ^3 ← ProductSpace{CartesianSpace, 0}()):
    -  1.0433705538503952
    -  0.3851936995690722
    - -1.9021899233277497
    julia> M₁ = randn(ℝ^4, ℝ^3)TensorMap(ℝ^4 ← ℝ^3): - 0.6621352468028409 -1.1095855631851348 1.8843966809177353 - -0.40920836002101335 0.7251711583996475 1.4989009103640432 - -1.716513700903317 -0.49760684313022174 -1.18561081606168 - -0.13931849790825576 -0.40751505293371354 -1.0211924654959947
    julia> M₂ = randn(ℝ^4 → ℝ^2) # alternative syntax for randn(ℝ^2, ℝ^4)TensorMap(ℝ^2 ← ℝ^4): - 0.34918004917967993 0.15925541443015548 … -0.4827859371324183 - 0.8048736494021879 1.358184615662905 0.4281717880703078
    julia> w = M₁ * v # matrix vector productTensorMap(ℝ^4 ← ProductSpace{CartesianSpace, 0}()): - -3.3210333268851393 - -2.9988187996718603 - 0.2726220756614156 - 1.6401689684509366
    julia> M₃ = M₂ * M₁ # matrix matrix productTensorMap(ℝ^2 ← ℝ^3): - -1.1122502342141964 -0.4652810374915757 0.4603382445744311 - 0.3610573990132997 0.045937548820949 3.42160680589773
    julia> space(M₃)ℝ^2 ← ℝ^3

    Note that for the construction of M₁, in accordance with how one specifies the dimensions of a matrix (e.g. randn(4,3)), the first space is the codomain and the second the domain. This is somewhat opposite to the general notation for a function f:domain→codomain, so that we also support this more mathemical notation, as illustrated in the construction of M₂. However, as this is confusing from the perspective of rows and columns, we also support the syntax codomain ← domain and actually use this as the default way of printing HomSpace instances.

    The 'matrix vector' or 'matrix matrix' product can be computed between any two TensorMap instances for which the domain of the first matches with the codomain of the second, e.g.

    julia> v′ = v ⊗ vTensorMap((ℝ^3 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()):
    -  1.0886221126420805   0.4018997636590656   -1.984688953831115
    -  0.4018997636590656   0.14837418618770865  -0.7327115738496257
    - -1.984688953831115   -0.7327115738496257    3.61832650440963
    julia> M₁′ = M₁ ⊗ M₁TensorMap((ℝ^4 ⊗ ℝ^4) ← (ℝ^3 ⊗ ℝ^3)): + -0.23296634595513221 + 0.4426821010962333 + -1.086434975045662
    julia> M₁ = randn(ℝ^4, ℝ^3)TensorMap(ℝ^4 ← ℝ^3): + 0.5706585871195738 -0.48465756197305737 0.931240359154148 + -0.19889481806739703 -0.33000862345441045 0.7989383462266909 + -0.4563798805253293 1.3910056260224475 0.4100362126831552 + 0.45745835044625327 0.6813057698381981 0.5773264475635865
    julia> M₂ = randn(ℝ^4 → ℝ^2) # alternative syntax for randn(ℝ^2, ℝ^4)TensorMap(ℝ^2 ← ℝ^4): + -0.3369137860608917 1.9212873975810139 … -1.2763726996893476 + 0.2383346275177461 -1.0916283641490128 -0.9890192448051662
    julia> w = M₁ * v # matrix vector productTensorMap(ℝ^4 ← ProductSpace{CartesianSpace, 0}()): + -1.3592255700347269 + -0.9677476740619193 + 0.2766167638034826 + -0.4321981753011116
    julia> M₃ = M₂ * M₁ # matrix matrix productTensorMap(ℝ^2 ← ℝ^3): + -1.3765612331163182 -0.6750645406341899 0.6804705601760521 + 0.7009491381701012 -2.8682021689217105 -1.9401780922955099
    julia> space(M₃)ℝ^2 ← ℝ^3

    Note that for the construction of M₁, in accordance with how one specifies the dimensions of a matrix (e.g. randn(4,3)), the first space is the codomain and the second the domain. This is somewhat opposite to the general notation for a function f:domain→codomain, so that we also support this more mathemical notation, as illustrated in the construction of M₂. However, as this is confusing from the perspective of rows and columns, we also support the syntax codomain ← domain and actually use this as the default way of printing HomSpace instances.

    The 'matrix vector' or 'matrix matrix' product can be computed between any two TensorMap instances for which the domain of the first matches with the codomain of the second, e.g.

    julia> v′ = v ⊗ vTensorMap((ℝ^3 ⊗ ℝ^3) ← ProductSpace{CartesianSpace, 0}()):
    +  0.054273318347686346  -0.1031300315121299   0.25310278625424315
    + -0.1031300315121299     0.1959674426309757  -0.4809453174576475
    +  0.25310278625424315   -0.4809453174576475   1.1803409550024684
    julia> M₁′ = M₁ ⊗ M₁TensorMap((ℝ^4 ⊗ ℝ^4) ← (ℝ^3 ⊗ ℝ^3)): [:, :, 1, 1] = - 0.43842308505865896 -0.2709512784562994 … -0.092247687996684 - -0.2709512784562994 0.16745148191108727 0.05701029404962832 - -1.1365642229880755 0.7024117565002466 0.2391421104487911 - -0.092247687996684 0.05701029404962832 0.019409643859412667 + 0.32565122305330824 -0.11350103586374548 … 0.26105253593170974 + -0.11350103586374548 0.039559148654062966 -0.0909860953854191 + -0.2604370978103843 0.09077159330670577 -0.2087747873219753 + 0.26105253593170974 -0.0909860953854191 0.20926814239300706 [:, :, 2, 1] = - -0.7346957107284584 0.4540516886139815 … 0.154585793963639 - 0.48016138394125263 -0.2967461004632583 -0.1010297565146287 - -0.3294830298868119 0.2036248802125517 0.06932583793377155 - -0.26983008015013715 0.16675856649488136 0.05677438504972831 + -0.2765739995523622 0.09639587761361945 … -0.2217106488314976 + -0.18832225479776932 0.06563700512263711 -0.15096520051849333 + 0.7937893052213482 -0.27666381091846043 0.6363271391416867 + 0.38879298801227974 -0.1355081871402363 0.3116690136196968 [:, :, 3, 1] = - 1.2477254613939188 -0.7711108754273872 … -0.2625313150487616 - 0.9924751242168985 -0.613362783364074 -0.2088246233452356 - -0.7850347103051181 0.4851618576637754 0.16517751799749458 - -0.676167525174392 0.4178804940714313 0.14227100036813028 + 0.5314203076236307 -0.18521888181098176 … 0.426003678567633 + 0.45592102785337235 -0.15890469701982474 0.3654810179731196 + 0.23399068579763044 -0.08155407792266067 0.18757448947726527 + 0.329456294873399 -0.11482723875365618 0.26410280437143363 [:, :, 1, 2] = - -0.7346957107284584 0.48016138394125263 … -0.26983008015013715 - 0.4540516886139815 -0.2967461004632583 0.16675856649488136 - 1.904618821531807 -1.2447662288929244 0.6995051716850598 - 0.154585793963639 -0.1010297565146287 0.05677438504972831 + -0.2765739995523622 -0.18832225479776932 … 0.38879298801227974 + 0.09639587761361945 0.06563700512263711 -0.1355081871402363 + 0.2211879602289613 0.15060929614445223 -0.31093424583997437 + -0.2217106488314976 -0.15096520051849333 0.3116690136196968 [:, :, 2, 2] = - 1.2311801220288727 -0.8046394481984895 … 0.45217281951587457 - -0.8046394481984895 0.5258732089746867 -0.29551816300123473 - 0.5521373692794241 -0.3608501308603346 0.2027822790183904 - 0.45217281951587457 -0.29551816300123473 0.16606851836756734 + 0.23489295237766794 0.1599411748734993 … -0.3301999933679581 + 0.1599411748734993 0.10890569155427486 -0.22483677925585116 + -0.6741613953988458 -0.4590438518610084 0.9477001588864883 + -0.3301999933679581 -0.22483677925585116 0.4641775520148198 [:, :, 3, 2] = - -2.090899352460304 1.366510123985565 … -0.767920013172305 - -1.6631588107849982 1.0869597094949794 -0.6108246838293945 - 1.3155366450581865 -0.8597707688945999 0.48315425446615884 - 1.1331004169477896 -0.7405393231527226 0.4161513016321097 + -0.4513326820785637 -0.3073173490296512 … 0.6344594297979169 + -0.3872115110490144 -0.26365654386321347 0.5443213050292325 + -0.19872715115968403 -0.13531548611402794 0.2793600375436362 + -0.279805628538734 -0.19052270624428408 0.39333583980526143 [:, :, 1, 3] = - 1.2477254613939188 0.9924751242168985 … -0.676167525174392 - -0.7711108754273872 -0.613362783364074 0.4178804940714313 - -3.2345927207320284 -2.5728839489363344 1.7528908582831126 - -0.2625313150487616 -0.2088246233452356 0.14227100036813028 + 0.5314203076236307 0.45592102785337235 … 0.329456294873399 + -0.18521888181098176 -0.15890469701982474 -0.11482723875365618 + -0.42499936385113485 -0.36461938699804136 -0.2634801751631824 + 0.426003678567633 0.3654810179731196 0.26410280437143363 [:, :, 2, 3] = - -2.090899352460304 -1.6631588107849982 … 1.1331004169477896 - 1.366510123985565 1.0869597094949794 -0.7405393231527226 - -0.937688683596542 -0.745863350171267 0.5081523589838298 - -0.767920013172305 -0.6108246838293945 0.4161513016321097 + -0.4513326820785637 -0.3872115110490144 … -0.279805628538734 + -0.3073173490296512 -0.26365654386321347 -0.19052270624428408 + 1.2953605787625846 1.111327734446397 0.8030643366125024 + 0.6344594297979169 0.5443213050292325 0.39333583980526143 [:, :, 3, 3] = - 3.550950851053777 2.8245239005145746 … -1.9243316925588514 - 2.8245239005145746 2.2467039390901573 -1.5306663161888483 - -2.2341610866467976 -1.7771131315323083 1.2107368323727454 - -1.9243316925588514 -1.5306663161888483 1.0428340515857883
    julia> w′ = M₁′ * v′TensorMap((ℝ^4 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()): - 11.029262358281777 9.959177174999937 … -5.447055805948382 - 9.959177174999937 8.992914193265376 -4.918569537229071 - -0.9053869989161623 -0.8175442056990159 0.44714626861453777 - -5.447055805948382 -4.918569537229071 2.690154245069409
    julia> w′ ≈ w ⊗ wtrue

    Another example involves checking that U from the singular value decomposition is a unitary, or at least a (left) isometric tensor

    julia> codomain(U)(ℝ^3 ⊗ ℝ^4)
    julia> domain(U)ProductSpace(ℝ^2)
    julia> space(U)(ℝ^3 ⊗ ℝ^4) ← ℝ^2
    julia> U'*U # should be the identity on the corresponding domain = codomainTensorMap(ℝ^2 ← ℝ^2): - 1.0000000000000002 -4.508897066200055e-18 - -4.508897066200055e-18 1.0
    julia> U'*U ≈ one(U'*U)true
    julia> P = U*U' # should be a projectorTensorMap((ℝ^3 ⊗ ℝ^4) ← (ℝ^3 ⊗ ℝ^4)): + 0.8672086065175466 0.7440036324821647 … 0.5376296883783027 + 0.7440036324821647 0.6383024810714398 0.4612482372493822 + 0.3818422699652681 0.32759365365413573 0.2367247500407932 + 0.5376296883783027 0.4612482372493822 0.3333058270563906
    julia> w′ = M₁′ * v′TensorMap((ℝ^4 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()): + 1.847494150236228 1.3153873839265933 … 0.587454811191622 + 1.3153873839265935 0.9365355606522547 0.41825877888145635 + -0.37598457846195 -0.2676952297773556 -0.1195532605735638 + 0.5874548111916221 0.4182587788814563 0.18679526273361033
    julia> w′ ≈ w ⊗ wtrue

    Another example involves checking that U from the singular value decomposition is a unitary, or at least a (left) isometric tensor

    julia> codomain(U)(ℝ^3 ⊗ ℝ^4)
    julia> domain(U)ProductSpace(ℝ^2)
    julia> space(U)(ℝ^3 ⊗ ℝ^4) ← ℝ^2
    julia> U'*U # should be the identity on the corresponding domain = codomainTensorMap(ℝ^2 ← ℝ^2): + 0.9999999999999999 -7.293640027689059e-17 + -7.293640027689059e-17 0.9999999999999998
    julia> U'*U ≈ one(U'*U)true
    julia> P = U*U' # should be a projectorTensorMap((ℝ^3 ⊗ ℝ^4) ← (ℝ^3 ⊗ ℝ^4)): [:, :, 1, 1] = - 0.4095069959871678 0.17773561199317067 … 0.15601471392647198 - 0.02315848959287907 0.12143544085722371 0.0007060569905477421 - -0.10673178063028572 0.2744404714716809 0.19943330827501027 + 0.3416932381010968 0.14934964196573444 … 0.14235680121805902 + 0.03158154271344805 0.22144808963871332 -0.16304288032076616 + -0.21531024593836898 0.16294020938467987 -0.12456169721518413 [:, :, 2, 1] = - 0.02315848959287907 0.056983007433400866 … 0.0035685870354084897 - 0.03546684106831507 -0.0721359266661257 0.06441388519586791 - -0.02700168563197551 -0.03889495410962346 -0.0011309167569583485 + 0.03158154271344805 0.060926491515634486 … -0.02630663680451983 + 0.04022323828454234 -0.10607970333971303 -0.06689898334486147 + -0.07491814897871639 -0.03995616880364679 -0.05326571315393467 [:, :, 3, 1] = - -0.10673178063028572 -0.07513089239697 … -0.037437714813510825 - -0.02700168563197551 0.016842189588199198 -0.039696946315877314 - 0.04068687346995598 -0.03812856601142406 -0.04436240284386048 + -0.21531024593836898 -0.16360752278559362 … -0.03149961060375841 + -0.07491814897871639 0.04709645107249069 0.17917788202875998 + 0.2168151613368829 -0.021533113866257867 0.14006850321185602 [:, :, 1, 2] = - 0.17773561199317067 0.14162515314453974 … 0.060494564797299435 - 0.056983007433400866 -0.055844139156593496 0.08875572769259622 - -0.07513089239697 0.044347623433010006 0.06950845274652907 + 0.14934964196573444 0.12480388925326101 … 0.012371269278788873 + 0.060926491515634486 -0.06306215681148851 -0.1367347289150932 + -0.16360752278559362 0.0017227940749899825 -0.10718640078009986 [:, :, 2, 2] = - 0.12143544085722371 -0.055844139156593496 … 0.058417727191482846 - -0.0721359266661257 0.21874022180714514 -0.14868344500245056 - 0.016842189588199198 0.20724146599203602 0.0878419659756666 + 0.22144808963871332 -0.06306215681148851 … 0.22613459544503703 + -0.10607970333971303 0.572805584638234 0.07015484247627575 + 0.04709645107249069 0.2922315661669347 0.06091128660991741 [:, :, 3, 2] = - 0.2744404714716809 0.044347623433010006 … 0.11292747126266256 - -0.03889495410962346 0.20724146599203602 -0.10207973062908748 - -0.03812856601142406 0.27061027412541394 0.15342379576174198 + 0.16294020938467987 0.0017227940749899825 … 0.12608604911574495 + -0.03995616880364679 0.2922315661669347 -0.0013108901713739058 + -0.021533113866257867 0.15883748219073573 0.0021783811590807686 [:, :, 1, 3] = - -0.03723934547177291 -0.10117521806916072 … -0.00466968871069136 - -0.06397858697579262 0.13206441975755476 -0.11667177198950872 - 0.04768346757866542 0.07361128668987574 0.004342435777255583 + -0.10382599506061424 -0.0887360045038385 … -0.00694724446871808 + -0.043917961405257507 0.04914093437525319 0.09722733947784155 + 0.11604252097557546 0.0011068953526235583 0.0762636171294071 [:, :, 2, 3] = - 0.0011986102331218026 -0.12439237756563373 … 0.014441610931166726 - -0.09084441083568068 0.21062949486561383 -0.1713346115494341 - 0.05548977249275616 0.1456337441629287 0.03361210763803917 + -0.032705710955840274 -0.03531148860242474 … 0.003974757882714428 + -0.019660234989537867 0.0352427155838692 0.03872138033309925 + 0.04514617368041488 0.00894062669727776 0.030544028640391144 [:, :, 3, 3] = - 0.20670268913204753 0.18417937653460154 … 0.06817378855404764 - 0.08044212897956597 -0.09772472960420217 0.12993032873134647 - -0.09607449781378515 0.028998001750854745 0.07568810703383767 + 0.07399616630420232 0.11403491471375753 … -0.03758705016048644 + 0.07151020039967233 -0.17142743345524472 -0.12516015926350552 + -0.1420062525018091 -0.0600906175994024 -0.09935792441677108 [:, :, 1, 4] = - 0.15601471392647198 0.060494564797299435 … 0.06024704427119297 - 0.0035685870354084897 0.058417727191482846 -0.009633607844733532 - -0.037437714813510825 0.11292747126266256 0.07788937483032735 + 0.14235680121805902 0.012371269278788873 … 0.10105810483704411 + -0.02630663680451983 0.22613459544503703 -0.013096767180680863 + -0.03149961060375841 0.12608604911574495 -0.007724695724406475 [:, :, 2, 4] = - 0.0007060569905477421 0.08875572769259622 … -0.009633607844733532 - 0.06441388519586791 -0.14868344500245056 0.12132289302579188 - -0.039696946315877314 -0.10207973062908748 -0.023043184343375798 + -0.16304288032076616 -0.1367347289150932 … -0.013096767180680863 + -0.06689898334486147 0.07015484247627575 0.14980821087275778 + 0.17917788202875998 -0.0013108901713739058 0.11744634758898916 [:, :, 3, 4] = - 0.19943330827501027 0.06950845274652907 … 0.07788937483032735 - -0.0011309167569583485 0.0878419659756666 -0.023043184343375798 - -0.04436240284386048 0.15342379576174198 0.10163397036744357
    julia> P*P ≈ Ptrue

    Here, the adjoint of a TensorMap results in a new tensor map (actually a simple wrapper of type AdjointTensorMap <: AbstractTensorMap) with domain and codomain interchanged.

    Our original tensor A living in ℝ^4 * ℝ^2 * ℝ^3 is isomorphic to e.g. a linear map ℝ^3 → ℝ^4 * ℝ^2. This is where the full power of permute emerges. It allows to specify a permutation where some indices go to the codomain, and others go to the domain, as in

    julia> A2 = permute(A,(1,2),(3,))TensorMap((ℝ^3 ⊗ ℝ^2) ← ℝ^4):
    + -0.12456169721518413  -0.10718640078009986    …  -0.007724695724406475
    + -0.05326571315393467   0.06091128660991741        0.11744634758898916
    +  0.14006850321185602   0.0021783811590807686      0.09214007614103475
    julia> P*P ≈ Ptrue

    Here, the adjoint of a TensorMap results in a new tensor map (actually a simple wrapper of type AdjointTensorMap <: AbstractTensorMap) with domain and codomain interchanged.

    Our original tensor A living in ℝ^4 * ℝ^2 * ℝ^3 is isomorphic to e.g. a linear map ℝ^3 → ℝ^4 * ℝ^2. This is where the full power of permute emerges. It allows to specify a permutation where some indices go to the codomain, and others go to the domain, as in

    julia> A2 = permute(A,(1,2),(3,))TensorMap((ℝ^3 ⊗ ℝ^2) ← ℝ^4):
     [:, :, 1] =
    - -1.7713227962791482  -1.6654441588823108
    - -0.3661364928846668   0.4201119694338522
    -  0.6249181393710261   0.11839611010215755
    +  1.559698370904021    -0.6538732046390204
    +  0.09602644522157724  -0.5853568628515657
    + -0.9118228769692547    1.186197821583879
     
     [:, :, 2] =
    - -1.1342284367830684   -0.01620275659939573
    -  0.08988953356351839  -1.683406193198217
    - -0.7633896348297693   -1.9354511137307029
    + 0.6209246273656582  -0.9488784917570874
    + 1.174100995600476    1.3569245936609886
    + 0.8147431107294355   0.4623455513149724
     
     [:, :, 3] =
    -  0.6428491839818875  -0.7801505609087104
    -  0.7027020258464614  -1.373717569571017
    - -1.42943426105255     0.1945426962157891
    + -0.42964284585426216   0.6816367293169704
    + -0.12782295103392152   0.29669669759580464
    +  0.25432347923136284  -1.0516095155825909
     
     [:, :, 4] =
    - -0.6339286229742425  -0.713617786902105
    - -0.5043010175077616   0.9663915579392787
    - -0.7660241120537823  -0.9979288631362535
    julia> codomain(A2)(ℝ^3 ⊗ ℝ^2)
    julia> domain(A2)ProductSpace(ℝ^4)

    In fact, tsvd(A, (1,3),(2,)) is a shorthand for tsvd(permute(A,(1,3),(2,))), where tsvd(A::TensorMap) will just compute the singular value decomposition according to the given codomain and domain of A.

    Note, finally, that the @tensor macro treats all indices at the same footing and thus does not distinguish between codomain and domain. The linear numbering is first all indices in the codomain, followed by all indices in the domain. However, when @tensor creates a new tensor (i.e. when using :=), the default syntax always creates a Tensor, i.e. with all indices in the codomain.

    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> codomain(A′)(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> domain(A′)ProductSpace{CartesianSpace, 0}()
    julia> @tensor A2′[(a,b);(c,)] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> codomain(A2′)(ℝ^3 ⊗ ℝ^2)
    julia> domain(A2′)ProductSpace(ℝ^4)
    julia> @tensor A2′′[a b; c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A2 ≈ A2′ == A2′′true

    As illustrated for A2′ and A2′′, additional syntax is available that enables one to immediately specify the desired codomain and domain indices.

    Complex tensors

    For applications in e.g. quantum physics, we of course want to work with complex tensors. Trying to create a complex-valued tensor with CartesianSpace indices is of course somewhat contrived and prints a (one-time) warning

    julia> A = randn(ComplexF64, ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)┌ Warning: scalartype(data) = ComplexF64 ⊈ ℝ)
    +  0.700722084783788   0.28289696338599857
    + -0.6773560414856941  1.0413143134178862
    + -0.5147053588941117  0.8258844807657844
    julia> codomain(A2)(ℝ^3 ⊗ ℝ^2)
    julia> domain(A2)ProductSpace(ℝ^4)

    In fact, tsvd(A, (1,3),(2,)) is a shorthand for tsvd(permute(A,(1,3),(2,))), where tsvd(A::TensorMap) will just compute the singular value decomposition according to the given codomain and domain of A.

    Note, finally, that the @tensor macro treats all indices at the same footing and thus does not distinguish between codomain and domain. The linear numbering is first all indices in the codomain, followed by all indices in the domain. However, when @tensor creates a new tensor (i.e. when using :=), the default syntax always creates a Tensor, i.e. with all indices in the codomain.

    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> codomain(A′)(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)
    julia> domain(A′)ProductSpace{CartesianSpace, 0}()
    julia> @tensor A2′[(a,b);(c,)] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> codomain(A2′)(ℝ^3 ⊗ ℝ^2)
    julia> domain(A2′)ProductSpace(ℝ^4)
    julia> @tensor A2′′[a b; c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A2 ≈ A2′ == A2′′true

    As illustrated for A2′ and A2′′, additional syntax is available that enables one to immediately specify the desired codomain and domain indices.

    Complex tensors

    For applications in e.g. quantum physics, we of course want to work with complex tensors. Trying to create a complex-valued tensor with CartesianSpace indices is of course somewhat contrived and prints a (one-time) warning

    julia> A = randn(ComplexF64, ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)┌ Warning: scalartype(data) = ComplexF64 ⊈ ℝ)
     └ @ TensorKit ~/work/TensorKit.jl/TensorKit.jl/src/tensors/tensor.jl:32
     TensorMap((ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4) ← ProductSpace{CartesianSpace, 0}()):
     [:, :, 1] =
    -  1.4278179122062056 - 0.8657601505397999im   …  -1.2246336619905054 - 0.019725115911454975im
    -  0.6642983486003922 + 0.17407284586432759im       0.855835684913677 - 0.12807657381131507im
    - -0.5459871176898289 - 0.6384488987162174im      0.12521356214231785 - 0.6831307713240845im
    +    0.4583457932485761 + 0.8389074968906994im   …    0.8728447215507993 + 0.68751647350748im
    + -0.007287712643472337 + 0.13383517135218453im      0.42031494313664325 + 0.687679790241553im
    +   -0.3676514381612984 - 1.0337169365704812im      -0.15815405366265867 + 0.2900872796878114im
     
     [:, :, 2] =
    - -0.47238957143738264 - 0.3684741819231097im  …  -0.5678550902714916 - 0.07493536226897908im
    -   0.3034734827867334 + 0.613709451972115im       0.6729632285511724 - 1.0624020330412067im
    -   0.8742771484116344 + 0.9242666712889925im      0.9003255470535453 - 0.5918280690741112im
    +  -0.3340368241074739 + 0.825168813582024im    …   0.9214198511241594 + 0.38563238029502295im
    + -0.21157664966768805 - 0.9032990929883263im      0.08184951115380616 - 0.07209413302782122im
    +   0.2570810123422488 + 0.24284865440175216im     -0.2876910423502672 - 0.7014263344016758im
     
     [:, :, 3] =
    -   1.8093282058852083 + 0.7500702559488152im   …    0.5694336700516613 - 0.002777873681227958im
    - -0.06203790859291279 + 1.587489706468873im        0.34883747225694633 - 0.7232314172404188im
    -  -0.5233821335766725 - 0.08962716634799962im     -0.08658732014576105 - 1.3218311282998325im
    + -1.1620175499485021 - 0.18431768110930571im  …  -0.3956217937124771 - 0.8076678468686628im
    + -1.0670487932551167 + 0.9214257326719123im      -0.8154442658563706 - 0.1523470397254786im
    + -0.8344706305246294 + 0.3653003168493904im      0.47082762770117875 - 1.172155965191732im
     
     [:, :, 4] =
    -   -0.072931079848766 + 1.0710465083915919im   …    -1.548723302460148 - 0.8787381400289785im
    -   1.1068852672187321 + 0.739295340662083im       -0.40757031928764675 + 0.6097002124177855im
    - -0.42706229728729317 - 0.22441580949598086im      -0.2118193416510239 + 0.5639691468547107im

    although most of the above operations will work in the expected way (at your own risk). Indeed, we instead want to work with complex vector spaces

    julia> A = randn(ComplexF64, ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)TensorMap((ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4) ← ProductSpace{ComplexSpace, 0}()):
    + -0.10608908455132449 + 0.24837286748281884im  …  -0.27983694893679684 + 0.09545181300259666im
    +   1.4641356919963453 - 0.2727490317457726im      -0.07019374348194789 + 2.1487452091079007im
    +   -0.335444549946074 - 0.2482104931329459im       0.11455797790933507 + 0.010568926070566528im

    although most of the above operations will work in the expected way (at your own risk). Indeed, we instead want to work with complex vector spaces

    julia> A = randn(ComplexF64, ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)TensorMap((ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4) ← ProductSpace{ComplexSpace, 0}()):
     [:, :, 1] =
    - -0.8744518636188947 - 0.930109336333406im   …   -0.6194044806420842 + 0.1752332898706966im
    -  0.1884665457674351 + 0.8262281624532132im     -0.03913097628753869 + 1.2455892347793005im
    -  1.1691575078454455 + 0.7085508181533555im     -0.03333859363760433 + 0.37781851695160207im
    +   0.7788064139061601 - 0.05456830220958977im  …   -0.40756499339269375 + 0.36188866697849276im
    + -0.18049661519999377 + 0.6308369562140623im        -1.0658217076638743 - 1.8071774880472815im
    +  0.17499771910818596 + 0.8494217585400238im      -0.040531617259888804 + 0.35515500100619374im
     
     [:, :, 2] =
    -  0.06101069528837904 - 1.1377222103032216im  …   0.9231462455406167 + 0.36246189045952193im
    -  -0.9107098436713766 + 0.5347534209610549im     -0.8626200127467858 + 0.12091393875416313im
    - -0.29380855377210063 - 1.9312037105256386im     -0.2500047063371581 + 0.36755023917550284im
    +  -0.43349521825093534 + 0.31307412488897973im  …  -1.0017597281082005 - 0.24638498594153954im
    +   -0.7059302806347362 + 0.11197642723141685im     -2.2022084080621847 + 0.4103151274766354im
    + -0.031085697178936215 - 0.9496622568190998im      -0.6204449952033575 + 0.025780578986006396im
     
     [:, :, 3] =
    - 0.07013179375308397 + 0.6025353558706721im   …   0.9904831212165547 + 1.5235011331378912im
    - -1.2600758395613523 - 0.24416555577452534im      0.4563449093337997 - 0.1555704805915915im
    - -0.7303910633719043 + 1.5087139977175237im      -0.6374656128623012 + 0.3000738139862144im
    +  0.03429850843670131 + 0.333960194825494im    …  -0.08665867361316644 - 0.802119245146945im
    + -0.18419067273343698 - 0.22648369792002898im        0.286148777023319 - 0.1476365204206457im
    +  -0.9933563008572986 - 0.26002398767881346im        1.140020181512733 + 0.8480949598470803im
     
     [:, :, 4] =
    - -0.30615717965438655 - 0.04156160007740747im  …  -0.5190651778644647 - 0.06340951159530557im
    - 0.024467229950524753 + 0.7594501428934173im      0.41852116777831566 - 0.6336122102358162im
    -  -0.9737405196453709 + 0.24521885917447347im     -0.5467505602732275 + 0.0877473197980667im

    where is obtained as \bbC+TAB and we also have the non-Unicode alternative ℂ^n == ComplexSpace(n). Most functionality works exactly the same

    julia> B = randn(ℂ^3 * ℂ^2 * ℂ^4);
    julia> C = im*A + (2.5-0.8im)*BTensorMap((ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4) ← ProductSpace{ComplexSpace, 0}()): + -0.32471338086307233 + 1.156845107676319im … 0.13699055832156434 + 0.43120551409395597im + 0.1998818280775332 + 0.17415060672908467im -0.5936445776368386 - 0.12613764568462857im + -0.3912821947016958 - 0.24493503281148066im -0.658647177875937 + 0.38789120470753924im

    where is obtained as \bbC+TAB and we also have the non-Unicode alternative ℂ^n == ComplexSpace(n). Most functionality works exactly the same

    julia> B = randn(ℂ^3 * ℂ^2 * ℂ^4);
    julia> C = im*A + (2.5-0.8im)*BTensorMap((ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4) ← ProductSpace{ComplexSpace, 0}()): [:, :, 1] = - 1.0802076152600975 - 0.922483312875436im … -0.873408147555151 - 0.3959885261830588im - -2.1004253812799027 + 0.5962096557919758im -6.341026226479492 + 1.5914088610565227im - 3.4561470319740097 - 0.1635458041953115im -1.7356501978431012 + 0.40116754424767537im + 2.0627053502854373 + 0.1362025585218889im … -1.6206671539103357 - 0.004755877574503964im + 2.482956273806794 - 1.176910448806668im -0.16855244863060181 - 0.43358812792695156im + -0.7294072066458365 + 0.136593062502046im 0.9102607428115548 - 0.44546465528156837im [:, :, 2] = - 7.368036521163727 - 1.932689884186983im … -0.8479052721136985 + 1.0784881276699534im - 0.8585625049118848 - 1.3565709399507173im -3.1728550890419838 + 0.11400115534531685im - 2.200608322922043 - 0.38001802973895005im -2.847872958849602 + 0.5436985639585536im + 3.4123457791661918 - 1.6256295875485902im … 1.953563831719613 - 1.548056958757184im + -0.08425737993669259 - 0.714800375769048im -5.989121636665419 - 0.4169903251217737im + 3.9968208934266447 - 1.0061764608933506im 0.4180418248466572 - 0.7624681644298099im [:, :, 3] = - -1.179136425069427 + 0.25464413589668555im … -5.868211194219736 + 2.380790340762745im - 1.7113817510143494 - 1.729585022038096im -1.3733464057664042 + 0.9455983129683583im - 0.2626725507598424 - 1.2972347588846613im -0.41141381534176175 - 0.601836812428526im + 1.014008695784997 - 0.3970515365586558im … 1.0670805562563819 - 0.17144629316818624im + 0.1555988054682291 - 0.16150750714886103im -3.065309883978055 + 1.314291626430903im + 3.9565960563180593 - 2.1762593628218574im -2.7725985099031796 + 1.755861317530685im [:, :, 4] = - 0.7219938584593736 - 0.5238955023366156im … 0.6070886931791524 - 0.6930425159712956im - -1.9842819018419255 + 0.4164133928140474im 0.16686873210720576 + 0.567879080779471im - -0.753210967550619 - 0.8111830449650044im 0.6639353063996334 - 0.7872890006564917im
    julia> scalarBA = dot(B,A)-0.6934928736026336 - 7.499021157151923im
    julia> scalarAA = dot(A,A)26.19130349823484 + 0.0im
    julia> normA² = norm(A)^226.191303498234838
    julia> U,S,Vd = tsvd(A,(1,3),(2,));
    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A′ ≈ Atrue
    julia> permute(A,(1,3),(2,)) ≈ U*S*Vdtrue

    However, trying the following

    julia> @tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]ERROR: SpaceMismatch("ProductSpace((ℂ^4)') ≠ ProductSpace(ℂ^4)")
    julia> @tensor d = A[a,b,c]*A[a,b,c]ERROR: SpaceMismatch("((ℂ^3)' ⊗ (ℂ^2)' ⊗ (ℂ^4)') ≠ (ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)")

    we obtain SpaceMismatch errors. The reason for this is that, with ComplexSpace, an index in a space ℂ^n can only be contracted with an index in the dual space dual(ℂ^n) == (ℂ^n)'. Because of the complex Euclidean inner product, the dual space is equivalent to the complex conjugate space, but not the the space itself.

    julia> dual(ℂ^3) == conj(ℂ^3) == (ℂ^3)'true
    julia> (ℂ^3)' == ℂ^3false
    julia> @tensor d = conj(A[a,b,c])*A[a,b,c]26.19130349823485 + 0.0im
    julia> d ≈ normA²true

    This might seem overly strict or puristic, but we believe that it can help to catch errors, e.g. unintended contractions. In particular, contracting two indices both living in ℂ^n would represent an operation that is not invariant under arbitrary unitary basis changes.

    It also makes clear the isomorphism between linear maps ℂ^n → ℂ^m and tensors in ℂ^m ⊗ (ℂ^n)':

    julia> m = randn(ComplexF64, ℂ^3, ℂ^4)TensorMap(ℂ^3 ← ℂ^4):
    - 0.23788560339836548 + 0.2379749698361991im   …  -0.23832474527957817 + 0.03559258563604777im
    - 0.28754115630887733 + 0.14141238852168014im     -0.09163433142613998 - 0.7013941847269325im
    -   2.046511012304249 - 0.5100467393594362im        0.6868352149017442 + 0.29818601082922275im
    julia> m2 = permute(m, (1,2), ())TensorMap((ℂ^3 ⊗ (ℂ^4)') ← ProductSpace{ComplexSpace, 0}()): - 0.23788560339836548 + 0.2379749698361991im … -0.23832474527957817 + 0.03559258563604777im - 0.28754115630887733 + 0.14141238852168014im -0.09163433142613998 - 0.7013941847269325im - 2.046511012304249 - 0.5100467393594362im 0.6868352149017442 + 0.29818601082922275im
    julia> codomain(m2)(ℂ^3 ⊗ (ℂ^4)')
    julia> space(m, 1)ℂ^3
    julia> space(m, 2)(ℂ^4)'

    Hence, spaces become their corresponding dual space if they are 'permuted' from the domain to the codomain or vice versa. Also, spaces in the domain are reported as their dual when probing them with space(A, i). Generalizing matrix vector and matrix matrix multiplication to arbitrary tensor contractions require that the two indices to be contracted have spaces which are each others dual. Knowing this, all the other functionality of tensors with CartesianSpace indices remains the same for tensors with ComplexSpace indices.

    Symmetries

    So far, the functionality that we have illustrated seems to be just a convenient (or inconvenient?) wrapper around dense multidimensional arrays, e.g. Julia's Base Array. More power becomes visible when involving symmetries. With symmetries, we imply that there is some symmetry action defined on every vector space associated with each of the indices of a TensorMap, and the TensorMap is then required to be equivariant, i.e. it acts as an intertwiner between the tensor product representation on the domain and that on the codomain. By Schur's lemma, this means that the tensor is block diagonal in some basis corresponding to the irreducible representations that can be coupled to by combining the different representations on the different spaces in the domain or codomain. For Abelian symmetries, this does not require a basis change and it just imposes that the tensor has some block sparsity. Let's clarify all of this with some examples.

    We start with a simple $ℤ₂$ symmetry:

    julia> V1 = ℤ₂Space(0=>3,1=>2)Rep[ℤ₂](0=>3, 1=>2)
    julia> dim(V1)5
    julia> V2 = ℤ₂Space(0=>1,1=>1)Rep[ℤ₂](0=>1, 1=>1)
    julia> dim(V2)2
    julia> A = randn(V1*V1*V2')TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)') ← ProductSpace{GradedSpace{Z2Irrep, Tuple{Int64, Int64}}, 0}()): + -5.8189024665099645 + 1.167144973963694im … -0.9485800528185018 + 0.30255041071341904im + -0.8489815887136657 + 0.4158277423125991im -4.181467263206675 + 0.7847889932083785im + 0.6038418338442582 - 0.5061323710321846im 1.9049495783826331 - 1.392356228464792im
    julia> scalarBA = dot(B,A)3.050782811158174 - 2.0623802265507005im
    julia> scalarAA = dot(A,A)21.69666970020099 + 0.0im
    julia> normA² = norm(A)^221.696669700200985
    julia> U,S,Vd = tsvd(A,(1,3),(2,));
    julia> @tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];
    julia> A′ ≈ Atrue
    julia> permute(A,(1,3),(2,)) ≈ U*S*Vdtrue

    However, trying the following

    julia> @tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]ERROR: SpaceMismatch("ProductSpace((ℂ^4)') ≠ ProductSpace(ℂ^4)")
    julia> @tensor d = A[a,b,c]*A[a,b,c]ERROR: SpaceMismatch("((ℂ^3)' ⊗ (ℂ^2)' ⊗ (ℂ^4)') ≠ (ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)")

    we obtain SpaceMismatch errors. The reason for this is that, with ComplexSpace, an index in a space ℂ^n can only be contracted with an index in the dual space dual(ℂ^n) == (ℂ^n)'. Because of the complex Euclidean inner product, the dual space is equivalent to the complex conjugate space, but not the the space itself.

    julia> dual(ℂ^3) == conj(ℂ^3) == (ℂ^3)'true
    julia> (ℂ^3)' == ℂ^3false
    julia> @tensor d = conj(A[a,b,c])*A[a,b,c]21.696669700200992 + 0.0im
    julia> d ≈ normA²true

    This might seem overly strict or puristic, but we believe that it can help to catch errors, e.g. unintended contractions. In particular, contracting two indices both living in ℂ^n would represent an operation that is not invariant under arbitrary unitary basis changes.

    It also makes clear the isomorphism between linear maps ℂ^n → ℂ^m and tensors in ℂ^m ⊗ (ℂ^n)':

    julia> m = randn(ComplexF64, ℂ^3, ℂ^4)TensorMap(ℂ^3 ← ℂ^4):
    +   1.4324564834528417 + 0.6183657141317256im   …  0.02567140557080219 + 0.3251851778720038im
    + -0.05419623897529476 + 0.42808730887025276im      0.7381247612895463 - 0.567948323211543im
    +    0.529153190429905 + 0.9721668954601086im      -1.1596966936118431 - 0.6028299523559205im
    julia> m2 = permute(m, (1,2), ())TensorMap((ℂ^3 ⊗ (ℂ^4)') ← ProductSpace{ComplexSpace, 0}()): + 1.4324564834528417 + 0.6183657141317256im … 0.02567140557080219 + 0.3251851778720038im + -0.05419623897529476 + 0.42808730887025276im 0.7381247612895463 - 0.567948323211543im + 0.529153190429905 + 0.9721668954601086im -1.1596966936118431 - 0.6028299523559205im
    julia> codomain(m2)(ℂ^3 ⊗ (ℂ^4)')
    julia> space(m, 1)ℂ^3
    julia> space(m, 2)(ℂ^4)'

    Hence, spaces become their corresponding dual space if they are 'permuted' from the domain to the codomain or vice versa. Also, spaces in the domain are reported as their dual when probing them with space(A, i). Generalizing matrix vector and matrix matrix multiplication to arbitrary tensor contractions require that the two indices to be contracted have spaces which are each others dual. Knowing this, all the other functionality of tensors with CartesianSpace indices remains the same for tensors with ComplexSpace indices.

    Symmetries

    So far, the functionality that we have illustrated seems to be just a convenient (or inconvenient?) wrapper around dense multidimensional arrays, e.g. Julia's Base Array. More power becomes visible when involving symmetries. With symmetries, we imply that there is some symmetry action defined on every vector space associated with each of the indices of a TensorMap, and the TensorMap is then required to be equivariant, i.e. it acts as an intertwiner between the tensor product representation on the domain and that on the codomain. By Schur's lemma, this means that the tensor is block diagonal in some basis corresponding to the irreducible representations that can be coupled to by combining the different representations on the different spaces in the domain or codomain. For Abelian symmetries, this does not require a basis change and it just imposes that the tensor has some block sparsity. Let's clarify all of this with some examples.

    We start with a simple $ℤ₂$ symmetry:

    julia> V1 = ℤ₂Space(0=>3,1=>2)Rep[ℤ₂](0=>3, 1=>2)
    julia> dim(V1)5
    julia> V2 = ℤ₂Space(0=>1,1=>1)Rep[ℤ₂](0=>1, 1=>1)
    julia> dim(V2)2
    julia> A = randn(V1*V1*V2')TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)') ← ProductSpace{GradedSpace{Z2Irrep, Tuple{Int64, Int64}}, 0}()): * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (): [:, :, 1] = - -1.4765334675423898 -1.3537092868222487 1.4250373019456672 - -0.3338917594749318 -2.2493186867873356 0.9631786878771779 - -0.7639299378901327 -1.7535528736298995 -1.2305143192578085 + 0.7190918824306693 0.8617937539190321 1.5999221495530527 + 0.08288065323300357 -0.7797170435643053 0.8565192629424715 + 0.518829650914116 -0.45435270240264436 -0.21964928348043458 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1), Irrep[ℤ₂](0)) ← (): [:, :, 1] = - -0.5526154415903979 -1.857772017676627 - -1.586661392262583 1.8249548647296294 + -1.7968169991000722 0.4547532800444926 + 0.1960188988217648 0.5056701331948987 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](0), Irrep[ℤ₂](1)) ← (): [:, :, 1] = - -0.041447683258307354 -0.06790651117280837 -1.0537667454990385 - 0.4929227072346302 0.29520080013318317 -0.4388251219998215 + 1.0717475943021886 -0.3398241464363732 0.7964369722508444 + 0.2236395915019626 -1.462323531353072 0.19539791524254188 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (): [:, :, 1] = - -0.5883969406219267 0.7130139436079371 - 0.46778172072927066 2.0033494284303046 - -0.6636575203964908 -0.41751144634856296
    julia> convert(Array, A)5×5×2 Array{Float64, 3}: + 0.2831389760508513 0.6154730270183192 + 1.9643739421069706 -0.455096066680525 + 0.5546364375981875 -1.843765603118894
    julia> convert(Array, A)5×5×2 Array{Float64, 3}: [:, :, 1] = - -1.47653 -1.35371 1.42504 0.0 0.0 - -0.333892 -2.24932 0.963179 0.0 0.0 - -0.76393 -1.75355 -1.23051 0.0 0.0 - 0.0 0.0 0.0 -0.552615 -1.85777 - 0.0 0.0 0.0 -1.58666 1.82495 + 0.719092 0.861794 1.59992 0.0 0.0 + 0.0828807 -0.779717 0.856519 0.0 0.0 + 0.51883 -0.454353 -0.219649 0.0 0.0 + 0.0 0.0 0.0 -1.79682 0.454753 + 0.0 0.0 0.0 0.196019 0.50567 [:, :, 2] = - 0.0 0.0 0.0 -0.588397 0.713014 - 0.0 0.0 0.0 0.467782 2.00335 - 0.0 0.0 0.0 -0.663658 -0.417511 - -0.0414477 -0.0679065 -1.05377 0.0 0.0 - 0.492923 0.295201 -0.438825 0.0 0.0

    Here, we create a 5-dimensional space V1, which has a three-dimensional subspace associated with charge 0 (the trivial irrep of $ℤ₂$) and a two-dimensional subspace with charge 1 (the non-trivial irrep). Similar for V2, where both subspaces are one- dimensional. Representing the tensor as a dense Array, we see that it is zero in those regions where the charges don't add to zero (modulo 2). Of course, the Tensor(Map) type in TensorKit.jl won't store these zero blocks, and only stores the non-zero information, which we can recognize also in the full Array representation.

    From there on, the resulting tensors support all of the same operations as the ones we encountered in the previous examples.

    julia> B = randn(V1'*V1*V2);
    julia> @tensor C[a,b] := A[a,c,d]*B[c,b,d]TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2)) ← ProductSpace{GradedSpace{Z2Irrep, Tuple{Int64, Int64}}, 0}()): + 0.0 0.0 0.0 0.283139 0.615473 + 0.0 0.0 0.0 1.96437 -0.455096 + 0.0 0.0 0.0 0.554636 -1.84377 + 1.07175 -0.339824 0.796437 0.0 0.0 + 0.22364 -1.46232 0.195398 0.0 0.0

    Here, we create a 5-dimensional space V1, which has a three-dimensional subspace associated with charge 0 (the trivial irrep of $ℤ₂$) and a two-dimensional subspace with charge 1 (the non-trivial irrep). Similar for V2, where both subspaces are one- dimensional. Representing the tensor as a dense Array, we see that it is zero in those regions where the charges don't add to zero (modulo 2). Of course, the Tensor(Map) type in TensorKit.jl won't store these zero blocks, and only stores the non-zero information, which we can recognize also in the full Array representation.

    From there on, the resulting tensors support all of the same operations as the ones we encountered in the previous examples.

    julia> B = randn(V1'*V1*V2);
    julia> @tensor C[a,b] := A[a,c,d]*B[c,b,d]TensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>3, 1=>2)) ← ProductSpace{GradedSpace{Z2Irrep, Tuple{Int64, Int64}}, 0}()): * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (): - -1.4521031945086722 -2.9990284383896597 -1.93012323162565 - -0.2728491337254653 -5.75798670164304 -4.203124721913632 - 1.6137246933191531 -7.0276373052178105 -3.8677975198076666 + 0.8832665181082007 2.8468688958040174 -2.405828852840004 + -3.0460266643561233 2.7857898025800716 2.1229337127186123 + -0.7825363480047234 0.11340586235932072 0.860661692286315 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (): - -2.1381756214626857 4.256299915254488 - 2.2051675007551106 -1.1640648077091738
    julia> U,S,V = tsvd(A,(1,3),(2,));
    julia> U'*U # should be the identity on the corresponding domain = codomainTensorMap(Rep[ℤ₂](0=>3, 1=>2) ← Rep[ℤ₂](0=>3, 1=>2)): + 1.9618951375131617 0.8927158106494867 + -0.5767801499082228 0.5279418957151346
    julia> U,S,V = tsvd(A,(1,3),(2,));
    julia> U'*U # should be the identity on the corresponding domain = codomainTensorMap(Rep[ℤ₂](0=>3, 1=>2) ← Rep[ℤ₂](0=>3, 1=>2)): * Data for sector (Irrep[ℤ₂](0),) ← (Irrep[ℤ₂](0),): - 1.0000000000000004 7.349544003461447e-17 2.980749023451156e-16 - 7.349544003461447e-17 1.0000000000000009 1.8772854309004578e-16 - 2.980749023451156e-16 1.8772854309004578e-16 1.0000000000000007 + 0.9999999999999994 1.801165184323214e-16 -2.632371639370919e-16 + 1.801165184323214e-16 0.9999999999999992 1.9163402825698768e-16 + -2.632371639370919e-16 1.9163402825698768e-16 0.9999999999999997 * Data for sector (Irrep[ℤ₂](1),) ← (Irrep[ℤ₂](1),): - 1.0 1.94841356657272e-16 - 1.94841356657272e-16 1.0
    julia> U'*U ≈ one(U'*U)true
    julia> P = U*U' # should be a projectorTensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)') ← (Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)')): + 0.9999999999999997 2.424284338464146e-16 + 2.424284338464146e-16 1.0000000000000002
    julia> U'*U ≈ one(U'*U)true
    julia> P = U*U' # should be a projectorTensorMap((Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)') ← (Rep[ℤ₂](0=>3, 1=>2) ⊗ Rep[ℤ₂](0=>1, 1=>1)')): * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](0)): [:, :, 1, 1] = - 0.8716579440218987 - -0.00400310994686197 - 0.056628242158396645 + 0.7904901216556869 + 0.16443912156241766 + -0.16345754770102247 [:, :, 2, 1] = - -0.00400310994686197 - 0.9486465340347665 - 0.08888357024668725 + 0.16443912156241766 + 0.5894301818672105 + -0.2304988128347927 [:, :, 3, 1] = - 0.056628242158396645 - 0.08888357024668725 - 0.8268658696129139 + -0.16345754770102247 + -0.2304988128347927 + 0.4151734144535272 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](0)): [:, :, 1, 1] = - -0.1146532240958318 - -0.30903434784931233 + 0.25270061227226437 + -0.21908741446517382 [:, :, 2, 1] = - -0.18746508846201704 - 0.07521179533474473 + -0.050282345201404496 + 0.3991290529562782 [:, :, 3, 1] = - 0.36330239276509824 - -0.00793900646180953 + 0.3858592516264413 + 0.118612881678714 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](1)): [:, :, 1, 1] = - -0.1146532240958318 - -0.18746508846201704 - 0.36330239276509824 + 0.25270061227226437 + -0.050282345201404496 + 0.3858592516264413 [:, :, 2, 1] = - -0.30903434784931233 - 0.07521179533474473 - -0.00793900646180953 + -0.21908741446517382 + 0.3991290529562782 + 0.118612881678714 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](1)): [:, :, 1, 1] = - 0.23749231521383138 - 0.028505537211552332 + 0.6173356423759709 + 0.14477248108936386 [:, :, 2, 1] = - 0.028505537211552332 - 0.11533733711659136 + 0.14477248108936386 + 0.5875706396476026 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](0)): [:, :, 1, 1] = - 0.44227536867893297 - -0.01945189042075225 + 0.43460026521128725 + -0.06778233584895381 [:, :, 2, 1] = - -0.01945189042075225 - 0.8279180735360204 + -0.06778233584895381 + 0.09427207938849505 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](1), Irrep[ℤ₂](0)): [:, :, 1, 1] = - -0.013741742557699367 - -0.45196056884105007 - 0.2045294590572672 + -0.09369133921312252 + -0.4766707889778405 + -0.07166115773639231 [:, :, 2, 1] = - 0.3114996682988392 - 0.06964394580660144 - 0.2005219151855583 + 0.11818773848727156 + 0.08068437870046805 + -0.24558521716118775 * Data for sector (Irrep[ℤ₂](1), Irrep[ℤ₂](0)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](1)): [:, :, 1, 1] = - -0.013741742557699367 - 0.3114996682988392 + -0.09369133921312252 + 0.11818773848727156 [:, :, 2, 1] = - -0.45196056884105007 - 0.06964394580660144 + -0.4766707889778405 + 0.08068437870046805 [:, :, 3, 1] = - 0.2045294590572672 - 0.2005219151855583 + -0.07166115773639231 + -0.24558521716118775 * Data for sector (Irrep[ℤ₂](0), Irrep[ℤ₂](1)) ← (Irrep[ℤ₂](0), Irrep[ℤ₂](1)): [:, :, 1, 1] = - 0.11729343200237788 - 0.03274988798468687 - 0.07240337567864455 + 0.14836726881653498 + 0.11060701104853563 + -0.30228149611757926 [:, :, 2, 1] = - 0.03274988798468687 - 0.46485239079427726 - -0.19640126127261479 + 0.11060701104853563 + 0.5232941683054569 + 0.05914778777628464 [:, :, 3, 1] = - 0.07240337567864455 - -0.19640126127261479 - 0.14766073498839133
    julia> P*P ≈ Ptrue

    We also support other abelian symmetries, e.g.

    julia> V = U₁Space(0=>2,1=>1,-1=>1)Rep[U₁](0=>2, 1=>1, -1=>1)
    julia> dim(V)4
    julia> A = randn(V*V, V)TensorMap((Rep[U₁](0=>2, 1=>1, -1=>1) ⊗ Rep[U₁](0=>2, 1=>1, -1=>1)) ← Rep[U₁](0=>2, 1=>1, -1=>1)): + -0.30228149611757926 + 0.05914778777628464 + 0.7994662182782255
    julia> P*P ≈ Ptrue

    We also support other abelian symmetries, e.g.

    julia> V = U₁Space(0=>2,1=>1,-1=>1)Rep[U₁](0=>2, 1=>1, -1=>1)
    julia> dim(V)4
    julia> A = randn(V*V, V)TensorMap((Rep[U₁](0=>2, 1=>1, -1=>1) ⊗ Rep[U₁](0=>2, 1=>1, -1=>1)) ← Rep[U₁](0=>2, 1=>1, -1=>1)): * Data for sector (Irrep[U₁](0), Irrep[U₁](0)) ← (Irrep[U₁](0),): [:, :, 1] = - -1.0098897514505747 -0.14607901544355878 - -1.409084413662135 -1.26983990901184 + 0.08056416651614404 0.29134865635754587 + -2.3092824123549653 -0.443854620170403 [:, :, 2] = - 0.5694102512389211 0.9853282506142192 - -1.2944171012116519 -1.9128999195006415 + 0.9791587421875722 -1.671352634827708 + -1.7030458056349886 0.7058380315291366 * Data for sector (Irrep[U₁](-1), Irrep[U₁](1)) ← (Irrep[U₁](0),): [:, :, 1] = - -0.26518761510881095 + -0.04857534166529732 [:, :, 2] = - -0.36760659022861103 + 0.6400737513539624 * Data for sector (Irrep[U₁](1), Irrep[U₁](-1)) ← (Irrep[U₁](0),): [:, :, 1] = - 0.33968402863585234 + -0.012415667050173555 [:, :, 2] = - 0.19203089888203437 + 0.4573914880659197 * Data for sector (Irrep[U₁](1), Irrep[U₁](0)) ← (Irrep[U₁](1),): [:, :, 1] = - -0.34616189450451323 -0.7288068260824412 + -2.7357313817707096 -0.9103224978921166 * Data for sector (Irrep[U₁](0), Irrep[U₁](1)) ← (Irrep[U₁](1),): [:, :, 1] = - -0.2640890536044551 - 2.620154882910416 + -0.28494765511473663 + 0.0012728112725709129 * Data for sector (Irrep[U₁](-1), Irrep[U₁](0)) ← (Irrep[U₁](-1),): [:, :, 1] = - -0.3514196615568599 -1.4287580709947933 + -1.5414414473651756 -0.28627785530470073 * Data for sector (Irrep[U₁](0), Irrep[U₁](-1)) ← (Irrep[U₁](-1),): [:, :, 1] = - -0.355883259419599 - 0.1287827926343905
    julia> dim(A)20
    julia> convert(Array, A)4×4×4 Array{Float64, 3}: + -0.06299279183128269 + 1.29148665317153
    julia> dim(A)20
    julia> convert(Array, A)4×4×4 Array{Float64, 3}: [:, :, 1] = - -1.00989 -0.146079 0.0 0.0 - -1.40908 -1.26984 0.0 0.0 - 0.0 0.0 0.0 0.339684 - 0.0 0.0 -0.265188 0.0 + 0.0805642 0.291349 0.0 0.0 + -2.30928 -0.443855 0.0 0.0 + 0.0 0.0 0.0 -0.0124157 + 0.0 0.0 -0.0485753 0.0 [:, :, 2] = - 0.56941 0.985328 0.0 0.0 - -1.29442 -1.9129 0.0 0.0 - 0.0 0.0 0.0 0.192031 - 0.0 0.0 -0.367607 0.0 + 0.979159 -1.67135 0.0 0.0 + -1.70305 0.705838 0.0 0.0 + 0.0 0.0 0.0 0.457391 + 0.0 0.0 0.640074 0.0 [:, :, 3] = - 0.0 0.0 -0.264089 0.0 - 0.0 0.0 2.62015 0.0 - -0.346162 -0.728807 0.0 0.0 - 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.284948 0.0 + 0.0 0.0 0.00127281 0.0 + -2.73573 -0.910322 0.0 0.0 + 0.0 0.0 0.0 0.0 [:, :, 4] = - 0.0 0.0 0.0 -0.355883 - 0.0 0.0 0.0 0.128783 - 0.0 0.0 0.0 0.0 - -0.35142 -1.42876 0.0 0.0
    julia> V = Rep[U₁×ℤ₂]((0, 0) => 2, (1, 1) => 1, (-1, 0) => 1)Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)
    julia> dim(V)4
    julia> A = randn(V*V, V)TensorMap((Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1) ⊗ Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)) ← Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)): + 0.0 0.0 0.0 -0.0629928 + 0.0 0.0 0.0 1.29149 + 0.0 0.0 0.0 0.0 + -1.54144 -0.286278 0.0 0.0
    julia> V = Rep[U₁×ℤ₂]((0, 0) => 2, (1, 1) => 1, (-1, 0) => 1)Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)
    julia> dim(V)4
    julia> A = randn(V*V, V)TensorMap((Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1) ⊗ Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)) ← Rep[U₁ × ℤ₂]((0, 0)=>2, (1, 1)=>1, (-1, 0)=>1)): * Data for sector (Irrep[U₁ × ℤ₂](0, 0), Irrep[U₁ × ℤ₂](0, 0)) ← (Irrep[U₁ × ℤ₂](0, 0),): [:, :, 1] = - 2.056152473145362 -1.13340618559997 - -1.089634812867178 1.3398491293272938 + 0.858815945355407 1.6469230533319583 + 1.2653806624068975 1.0897706702356722 [:, :, 2] = - -1.1087228659829818 -0.6382008324536271 - 0.9120395894019876 1.1651717756343107 + 1.4632039720691565 0.24860816542578773 + 0.13098236808071795 -1.2403679965206778 * Data for sector (Irrep[U₁ × ℤ₂](1, 1), Irrep[U₁ × ℤ₂](0, 0)) ← (Irrep[U₁ × ℤ₂](1, 1),): [:, :, 1] = - 0.3445612355313564 1.1132418739664707 + -0.7265350449205807 1.5861169866660991 * Data for sector (Irrep[U₁ × ℤ₂](0, 0), Irrep[U₁ × ℤ₂](1, 1)) ← (Irrep[U₁ × ℤ₂](1, 1),): [:, :, 1] = - 0.7738817079291348 - -1.8997255028893962 + 0.9434948387116404 + 0.2724021938452269 * Data for sector (Irrep[U₁ × ℤ₂](-1, 0), Irrep[U₁ × ℤ₂](0, 0)) ← (Irrep[U₁ × ℤ₂](-1, 0),): [:, :, 1] = - -1.009927579472485 0.760707244150626 + 2.5425917010875376 0.4469812371061934 * Data for sector (Irrep[U₁ × ℤ₂](0, 0), Irrep[U₁ × ℤ₂](-1, 0)) ← (Irrep[U₁ × ℤ₂](-1, 0),): [:, :, 1] = - 1.0470437346908552 - -0.27060578640437744
    julia> dim(A)16
    julia> convert(Array, A)4×4×4 Array{Float64, 3}: + 0.3219779329310578 + 0.9533401298409284
    julia> dim(A)16
    julia> convert(Array, A)4×4×4 Array{Float64, 3}: [:, :, 1] = - 2.05615 -1.13341 0.0 0.0 - -1.08963 1.33985 0.0 0.0 - 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 + 0.858816 1.64692 0.0 0.0 + 1.26538 1.08977 0.0 0.0 + 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 [:, :, 2] = - -1.10872 -0.638201 0.0 0.0 - 0.91204 1.16517 0.0 0.0 - 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 + 1.4632 0.248608 0.0 0.0 + 0.130982 -1.24037 0.0 0.0 + 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 [:, :, 3] = - 0.0 0.0 0.773882 0.0 - 0.0 0.0 -1.89973 0.0 - 0.344561 1.11324 0.0 0.0 - 0.0 0.0 0.0 0.0 + 0.0 0.0 0.943495 0.0 + 0.0 0.0 0.272402 0.0 + -0.726535 1.58612 0.0 0.0 + 0.0 0.0 0.0 0.0 [:, :, 4] = - 0.0 0.0 0.0 1.04704 - 0.0 0.0 0.0 -0.270606 - 0.0 0.0 0.0 0.0 - -1.00993 0.760707 0.0 0.0

    Here, the dim of a TensorMap returns the number of linearly independent components, i.e. the number of non-zero entries in the case of an abelian symmetry. Also note that we can use × (obtained as \times+TAB) to combine different symmetry groups. The general space associated with symmetries is a GradedSpace, which is parametrized to the type of symmetry. For a group G, the fully specified type can be obtained as Rep[G], while for more general sectortypes I it can be constructed as Vect[I]. Furthermore, ℤ₂Space (also Z2Space as non-Unicode alternative) and U₁Space (or U1Space) are just convenient synonyms, e.g.

    julia> Rep[U₁](0=>3,1=>2,-1=>1) == U1Space(-1=>1,1=>2,0=>3)true
    julia> V = U₁Space(1=>2,0=>3,-1=>1)Rep[U₁](0=>3, 1=>2, -1=>1)
    julia> for s in sectors(V) + 0.0 0.0 0.0 0.321978 + 0.0 0.0 0.0 0.95334 + 0.0 0.0 0.0 0.0 + 2.54259 0.446981 0.0 0.0

    Here, the dim of a TensorMap returns the number of linearly independent components, i.e. the number of non-zero entries in the case of an abelian symmetry. Also note that we can use × (obtained as \times+TAB) to combine different symmetry groups. The general space associated with symmetries is a GradedSpace, which is parametrized to the type of symmetry. For a group G, the fully specified type can be obtained as Rep[G], while for more general sectortypes I it can be constructed as Vect[I]. Furthermore, ℤ₂Space (also Z2Space as non-Unicode alternative) and U₁Space (or U1Space) are just convenient synonyms, e.g.

    julia> Rep[U₁](0=>3,1=>2,-1=>1) == U1Space(-1=>1,1=>2,0=>3)true
    julia> V = U₁Space(1=>2,0=>3,-1=>1)Rep[U₁](0=>3, 1=>2, -1=>1)
    julia> for s in sectors(V) @show s, dim(V, s) end(s, dim(V, s)) = (Irrep[U₁](0), 3) (s, dim(V, s)) = (Irrep[U₁](1), 2) (s, dim(V, s)) = (Irrep[U₁](-1), 1)
    julia> U₁Space(-1=>1,0=>3,1=>2) == GradedSpace(Irrep[U₁](1)=>2, Irrep[U₁](0)=>3, Irrep[U₁](-1)=>1)true
    julia> supertype(GradedSpace)ElementarySpace

    Note that GradedSpace is not immediately parameterized by some group G, but actually by the set of irreducible representations of G, denoted as Irrep[G]. Indeed, GradedSpace also supports a grading that is derived from the fusion ring of a (unitary) pre-fusion category. Note furthermore that the order in which the charges and their corresponding subspace dimensionality are specified is irrelevant, and that the charges, henceforth called sectors (which is a more general name for charges or quantum numbers) are of a specific type, in this case Irrep[U₁] == U1Irrep. However, the Vect[I] constructor automatically converts the keys in the list of Pairs it receives to the correct type. Alternatively, we can directly create the sectors of the correct type and use the generic GradedSpace constructor. We can probe the subspace dimension of a certain sector s in a space V with dim(V, s). Finally, note that GradedSpace is also a subtype of EuclideanSpace, which implies that it still has the standard Euclidean inner product and we assume all representations to be unitary.

    TensorKit.jl also allows for non-abelian symmetries such as SU₂. In this case, the vector space is characterized via the spin quantum number (i.e. the irrep label of SU₂) for each of its subspaces, and is created using SU₂Space (or SU2Space or Rep[SU₂] or Vect[Irrep[SU₂]])

    julia> V = SU₂Space(0=>2,1/2=>1,1=>1)Rep[SU₂](0=>2, 1/2=>1, 1=>1)
    julia> dim(V)7
    julia> V == Vect[Irrep[SU₂]](0=>2, 1=>1, 1//2=>1)true

    Note that now V has a two-dimensional subspace with spin zero, and two one-dimensional subspaces with spin 1/2 and spin 1. However, a subspace with spin j has an additional 2j+1 dimensional degeneracy on which the irreducible representation acts. This brings the total dimension to 2*1 + 1*2 + 1*3. Creating a tensor with SU₂ symmetry yields

    julia> A = randn(V*V, V)TensorMap((Rep[SU₂](0=>2, 1/2=>1, 1=>1) ⊗ Rep[SU₂](0=>2, 1/2=>1, 1=>1)) ← Rep[SU₂](0=>2, 1/2=>1, 1=>1)):
     * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 0), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()):
     [:, :, 1] =
    -  1.3259875541124793  -0.19266592925237666
    - -0.5520547012628059   0.2562072890360444
    + -0.5462348122535514  0.683178568839702
    +  0.3832954614749589  0.8065372169763511
     
     [:, :, 2] =
    - -0.030473525256306932  -1.7370143968885938
    - -0.8150999097461114    -2.407178127261767
    +  1.336437613540811   -0.17097536652049072
    + -1.0341456396239712   0.14734440293476841
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2, 1/2), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()):
     [:, :, 1] =
    - 1.374802164760629
    + -0.4934737721265092
     
     [:, :, 2] =
    - -0.5633406674953472
    + 0.3856773884877695
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 0, (false, false), ()) ← FusionTree{Irrep[SU₂]}((0,), 0, (false,), ()):
     [:, :, 1] =
    - 0.7986236623057694
    + -1.0383930805119277
     
     [:, :, 2] =
    - 3.9181380117410867
    + -0.28454499021618135
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2, 0), 1/2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()):
     [:, :, 1] =
    - -1.0161624622368628  -0.2811713232131734
    + -0.15393111369915013  -1.30561121010476
     * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1/2), 1/2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()):
     [:, :, 1] =
    - -0.5444761565492681
    - -1.4984499896659906
    + -1.384584577981802
    + -0.48549853801012166
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1/2), 1/2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()):
     [:, :, 1] =
    - 0.9935002979857978
    + -0.8736930280333401
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2, 1), 1/2, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1/2,), 1/2, (false,), ()):
     [:, :, 1] =
    - -1.3900395589412038
    + 0.13380176531551136
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 0), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()):
     [:, :, 1] =
    - -1.5225871506461626  -0.9869212545821906
    + -0.8785312324317226  0.3238669559533814
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1/2, 1/2), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()):
     [:, :, 1] =
    - 0.2500910477957828
    + 1.9449032736346377
     * Data for fusiontree FusionTree{Irrep[SU₂]}((0, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()):
     [:, :, 1] =
    - -0.37739590546883833
    - -0.3942934144920045
    + -0.19601510174609868
    + -0.7099006633960786
     * Data for fusiontree FusionTree{Irrep[SU₂]}((1, 1), 1, (false, false), ()) ← FusionTree{Irrep[SU₂]}((1,), 1, (false,), ()):
     [:, :, 1] =
    - -0.1315128953935696
    julia> dim(A)24
    julia> convert(Array, A)7×7×7 Array{Float64, 3}: + 1.1180770989577846
    julia> dim(A)24
    julia> convert(Array, A)7×7×7 Array{Float64, 3}: [:, :, 1] = - 1.32599 -0.192666 0.0 0.0 0.0 0.0 0.0 - -0.552055 0.256207 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.972132 0.0 0.0 0.0 - 0.0 0.0 -0.972132 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.461086 - 0.0 0.0 0.0 0.0 0.0 -0.461086 0.0 - 0.0 0.0 0.0 0.0 0.461086 0.0 0.0 + -0.546235 0.683179 0.0 0.0 0.0 0.0 0.0 + 0.383295 0.806537 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 -0.348939 0.0 0.0 0.0 + 0.0 0.0 0.348939 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 -0.599517 + 0.0 0.0 0.0 0.0 0.0 0.599517 0.0 + 0.0 0.0 0.0 0.0 -0.599517 0.0 0.0 [:, :, 2] = - -0.0304735 -1.73701 0.0 0.0 0.0 0.0 0.0 - -0.8151 -2.40718 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -0.398342 0.0 0.0 0.0 - 0.0 0.0 0.398342 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 2.26214 - 0.0 0.0 0.0 0.0 0.0 -2.26214 0.0 - 0.0 0.0 0.0 0.0 2.26214 0.0 0.0 + 1.33644 -0.170975 0.0 0.0 0.0 0.0 0.0 + -1.03415 0.147344 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.272715 0.0 0.0 0.0 + 0.0 0.0 -0.272715 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 -0.164282 + 0.0 0.0 0.0 0.0 0.0 0.164282 0.0 + 0.0 0.0 0.0 0.0 -0.164282 0.0 0.0 [:, :, 3] = - 0.0 0.0 -0.544476 0.0 0.0 0.0 0.0 - 0.0 0.0 -1.49845 0.0 0.0 0.0 0.0 - -1.01616 -0.281171 0.0 0.0 0.0 -0.80254 0.0 - 0.0 0.0 0.0 0.0 1.13496 0.0 0.0 - 0.0 0.0 0.0 0.81119 0.0 0.0 0.0 - 0.0 0.0 -0.573598 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 -1.38458 0.0 0.0 0.0 0.0 + 0.0 0.0 -0.485499 0.0 0.0 0.0 0.0 + -0.153931 -1.30561 0.0 0.0 0.0 0.0772505 0.0 + 0.0 0.0 0.0 0.0 -0.109249 0.0 0.0 + 0.0 0.0 0.0 -0.713367 0.0 0.0 0.0 + 0.0 0.0 0.504427 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 [:, :, 4] = - 0.0 0.0 0.0 -0.544476 0.0 0.0 0.0 - 0.0 0.0 0.0 -1.49845 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 -1.13496 - -1.01616 -0.281171 0.0 0.0 0.0 0.80254 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.573598 0.0 0.0 0.0 - 0.0 0.0 -0.81119 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 -1.38458 0.0 0.0 0.0 + 0.0 0.0 0.0 -0.485499 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.109249 + -0.153931 -1.30561 0.0 0.0 0.0 -0.0772505 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 -0.504427 0.0 0.0 0.0 + 0.0 0.0 0.713367 0.0 0.0 0.0 0.0 [:, :, 5] = - 0.0 0.0 0.0 0.0 -0.377396 0.0 0.0 - 0.0 0.0 0.0 0.0 -0.394293 0.0 0.0 - 0.0 0.0 0.250091 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - -1.52259 -0.986921 0.0 0.0 0.0 -0.0929937 0.0 - 0.0 0.0 0.0 0.0 0.0929937 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.196015 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.709901 0.0 0.0 + 0.0 0.0 1.9449 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + -0.878531 0.323867 0.0 0.0 0.0 0.7906 0.0 + 0.0 0.0 0.0 0.0 -0.7906 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 [:, :, 6] = - 0.0 0.0 0.0 0.0 0.0 -0.377396 0.0 - 0.0 0.0 0.0 0.0 0.0 -0.394293 0.0 - 0.0 0.0 0.0 0.176841 0.0 0.0 0.0 - 0.0 0.0 0.176841 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 -0.0929937 - -1.52259 -0.986921 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0929937 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 -0.196015 0.0 + 0.0 0.0 0.0 0.0 0.0 -0.709901 0.0 + 0.0 0.0 0.0 1.37525 0.0 0.0 0.0 + 0.0 0.0 1.37525 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.7906 + -0.878531 0.323867 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 -0.7906 0.0 0.0 [:, :, 7] = - 0.0 0.0 0.0 0.0 0.0 0.0 -0.377396 - 0.0 0.0 0.0 0.0 0.0 0.0 -0.394293 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.250091 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 0.0 0.0 0.0 -0.0929937 - -1.52259 -0.986921 0.0 0.0 0.0 0.0929937 0.0
    julia> norm(A) ≈ norm(convert(Array, A))true

    In this case, the full Array representation of the tensor has again many zeros, but it is less obvious to recognize the dense blocks, as there are additional zeros and the numbers in the original tensor data do not match with those in the Array. The reason is of course that the original tensor data now needs to be transformed with a construction known as fusion trees, which are made up out of the Clebsch-Gordan coefficients of the group. Indeed, note that the non-zero blocks are also no longer labeled by a list of sectors, but by pair of fusion trees. This will be explained further in the manual. However, the Clebsch-Gordan coefficients of the group are only needed to actually convert a tensor to an Array. For working with tensors with SU₂Space indices, e.g. contracting or factorizing them, the Clebsch-Gordan coefficients are never needed explicitly. Instead, recoupling relations are used to symbolically manipulate the basis of fusion trees, and this only requires what is known as the topological data of the group (or its representation theory).

    In fact, this formalism extends beyond the case of group representations on vector spaces, and can also deal with super vector spaces (to describe fermions) and more general (unitary) fusion categories. Support for all of these generalizations is present in TensorKit.jl. Indeed, all of these concepts will be explained throughout the remainder of this manual, including several details regarding their implementation. However, to just use tensors and their manipulations (contractions, factorizations, ...) in higher level algorithms (e.g. tensoer network algorithms), one does not need to know or understand most of these details, and one can immediately refer to the general interface of the TensorMap type, discussed on the last page. Adhering to this interface should yield code and algorithms that are oblivious to the underlying symmetries and can thus work with arbitrary symmetric tensors.

    + 0.0 0.0 0.0 0.0 0.0 0.0 -0.196015 + 0.0 0.0 0.0 0.0 0.0 0.0 -0.709901 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 1.9449 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 0.0 0.7906 + -0.878531 0.323867 0.0 0.0 0.0 -0.7906 0.0
    julia> norm(A) ≈ norm(convert(Array, A))true

    In this case, the full Array representation of the tensor has again many zeros, but it is less obvious to recognize the dense blocks, as there are additional zeros and the numbers in the original tensor data do not match with those in the Array. The reason is of course that the original tensor data now needs to be transformed with a construction known as fusion trees, which are made up out of the Clebsch-Gordan coefficients of the group. Indeed, note that the non-zero blocks are also no longer labeled by a list of sectors, but by pair of fusion trees. This will be explained further in the manual. However, the Clebsch-Gordan coefficients of the group are only needed to actually convert a tensor to an Array. For working with tensors with SU₂Space indices, e.g. contracting or factorizing them, the Clebsch-Gordan coefficients are never needed explicitly. Instead, recoupling relations are used to symbolically manipulate the basis of fusion trees, and this only requires what is known as the topological data of the group (or its representation theory).

    In fact, this formalism extends beyond the case of group representations on vector spaces, and can also deal with super vector spaces (to describe fermions) and more general (unitary) fusion categories. Support for all of these generalizations is present in TensorKit.jl. Indeed, all of these concepts will be explained throughout the remainder of this manual, including several details regarding their implementation. However, to just use tensors and their manipulations (contractions, factorizations, ...) in higher level algorithms (e.g. tensoer network algorithms), one does not need to know or understand most of these details, and one can immediately refer to the general interface of the TensorMap type, discussed on the last page. Adhering to this interface should yield code and algorithms that are oblivious to the underlying symmetries and can thus work with arbitrary symmetric tensors.

    diff --git a/previews/PR187/objects.inv b/previews/PR187/objects.inv index 7db112fe2662c24ad420bbd7a80c657722c50915..8bbf10986e0e4621e7c526093ef7a3fb12157053 100644 GIT binary patch delta 4633 zcmV+!66WoJHTfl8tUreNEtlv#d6F;jDdaO<)j_=qw!c}SbeA})hAz{lte=S5BzI892j zDM>~08gqd$f)vk5+Fau={(v+Qa*0M4xU4~2lA>IaGQnAquag^2mspc@*@|fbS4oUG zG2+DN#+m$CkymxeGz$1q_>S{1Piv?a3DP7ZDHs}})_>X$k7J-!XM|No{&CPbsG&_E0DxQ2HU7pgbAjNa^qR0!n1Un~8%eEwa zb5;~cQ^3+ySL?FaZn`)7v$r4^HVEFfVQ?$Kh2*5fsqKu~xcx;mr9VpY)BKhxPZvqT zOn;0G6k3g?1`98=m1C+6MiSfBva>gb6oCR@+e~sRNlDt=->xHh&RejyN_$q@Tu886 zN@x1lA(A9wW91k`2GfV_mRF!HnO=QqeXp#&*lCEL6cuxwYOA(w;hfZOit-1$hYCi6 z*MZfuxKCvpW*G$|w!ucW%K_Fk zE6U9^t+HzL5=LHRd@lfm0T4r(dnFpp0eb5;vx0#F&W3@q;EciONr~wa6hs2WH-HHO zT;1i?G{UiPnvwR2!O+KL5FN>1;KdK%b^vr`8E)u+$nCE!jh_}$6T5Fv+%V-fk2^sCvKYHIYe zy2{C$7+dmGyDI<@Z+Lje6jrntomLfHbJsXei^UJpe6IlMCR+9S?ThAuI2UQV85iVo z8J$5)Pb!M@NT;rvYqx%$SAQTS&wD4hYUgOJh9P(SDZ-WR0hYGv$Oh=($4VKn{Ul}% z@+m=3x1;BdPI45o*aBqB5+2|tbGl*ajXuofvf=ncun}=GGHgcA-eP7!`@s8iQf8Dv zPGyiB$k{m-MHvFCmN&5+d4y9kt*!VQ7OaG&A~M$DY8k_f8&^h(6o07zybdmx`JW!0 zb`nLF>E*%}xWu|6dbu*$*09bv8CMt-NlC_HxAL~%2t3nml@*Wz>65szuF+u8*jC2| z%9KjyFg?$Um#+(&_q(T4)lQ(l^%|^)<@$MKx`WnAx}eU!2<3*n^*d+VDpMggSpOQ+ zw8zXiBuJ8Q(-L8_Wq*7$dP3(ZEg(qRT=$|*CFZ}<`oU{IN0y9aq#v&XlEBq~r(3Y) z^({voGBXpO^pM0{z$&d{@*jR^VKC*uP{IPEPKp>XCQWhK;d@&4za+GM#L`Pewodp2 zm2^#)hkDX%0jkY4DS6Jzi4(5o5~W3>xkCx%IMrsRmE3(7((6)CgU#$Z+_fc^pdgL2)Fk~v~ae*vpAr^a{NYzsT- zohRZ$W=g;Mqx^V}UZ*EwyeOD>4RSqr^u*(PFg`mNf61BJUmrbnHrTgCCXhdL%K=q0 z{IDC#JPmIL?SFY5i5Z1J=G72s#24Le2>8Tk(+#QFDL)`IWQUB ziP!$%`1IiH_~4sIA!bXG0(>MgK$apGHxH#T^d;QhSBM?RTHF>=mET>lD*6jSD3iQV z%gA@&XIfQ7ZP2RtU;qzM){cZaGJ$eJSjv&#*m!#~pt3x(7IY+xw zCRQ=u2~3gw6O)%sC*GB*YW5``5`4NWoOz1~jW|=D>Uk+6z-D!E1pmi+o)<@tPuv~M z=`O~B%YSfvU!3$pPb1dYnVhw}Q*X`G@XShG9LdkIx0Z10_zZTtJ35CiP<1p>tv-6M zO~shHR$N)gs2yWfZXNA85A2VL`~Y7D*Un*!=nGGZA~jOwO&*`b8$^E?mYmNccFV-v z63$S!IG08k|IKZi`*|^axpfawmNE_|;;!HZEOA01W>)X^rI6-HwVFb+**V0G0 zE`N{Mv7fGcF#&x%m=vBd9!H+%fbW#Y3@t$y$TKUj0|>JgAh_8dT;rIAjO>P3a)S+gV8pDugM-+G2seFK6Yjh74*X<{d@DrK zP26xrtVl%9nb?`AWD^4WK()U(;+ z6F(CZvr>(|ECuJ`41CN&;ND!-iVByw$MDC|TwuypYDrenmQ-n&j;(oG4Ci&X`F}{s zs?N7*I=G)fQ9REXxp<}rzX+3LLC*$Y=|(G2;L{qEgHi?~kcNHoKxrG$*bQGa3<9lq zqHmzL0ju`&eOR?`9_?h+Y$wuU+oFt`$0umo!a!_#IUbX`BBKI32^^WOvnZO56{ji9Lus@YFfO)_fbgp9 zv4{N_Vi>V882}eW>sVBfk#Zfx5aB(XZ?i$mvn(=9^Z53B@DOJVHO@l}`U5&(0<2c?~(PAIl3z1@xxcIn>vw{mtrV?+iFIDEtMkOZv8%Uv5UGf_dQb zhXGzKV4E5fy9du^F97`X!JHcmh~?T)AVLdG2V(M_TxTb=Zi&`=9V%WfMfpAwTIlvoVBz-E zONW4Rsu6v0RTC-Vf;D7E)@YoZd(8k|f z)u&|!zZZVmTqdm;4v(r5a5J?gwK$R+PVajr6SnXxT;g&Kjf`BiSbL13+goQczr`BL zohHVh2m|ZrETS7|(JWL#@>Zr~=f_Oucj(C!Lq6bSN(pFWAmV{2I6kwjb)yP}0xdl- zN3EP}ylXf9&&6B1?0+ZBa$hi~PWkxx#P*2HrDu_9;aet19lV`pue>Vex+Wr?1 zvJ&a(tT1L?)CX$0p(piJQW&j zt*Fyq7(#kO?`$@10TIK--E}T0loi`8VH?bk#a2)x-2$h$wVE4p9PCXB(f(0A9}&54 zRdZ|0(yAmlO@AD-DpkMlaFb2iG>=*`P2qs!`d7#FOhe0I{mJ*My+>R;pK9xhTU{pX zvA@AN75O;NWSh-Nq^VU;lSF_yb8xIROK|@wfyN-(GXkGf=-&{~1uofBEFA*kbN!K& z#a#||Tl7Qm^G+uY%xSOPs?_$0uZ~(Y?2UNftxoSnCV#>z={rCBRpA21iysv;sClzg z+y4V>(17?if*+o%$1P${#Z86=H53-Atoi0=?tUL9*n%vh&a!#au@7|LWlZ5C-a8CP zTJq_JMwa#RZB}eUD5~G4hL2@A1-Hj1jkCbb<5@Sj{QQe*{JAm~>bHfg#Er=?d)4NU$yG>!D1+I`As4LHIOtYGYP&hrkZ zI@^m7_jayx$CH~wDzTv|to9Xr)bY=c46HQWJ%0`hMq)8WXTJrdj~K}fjpu^L-rDg< zFX(P#*1ig1T=iAWb=<~wG{EZ)EkCW=uvqweOn|j(SX_SCjPH}pjm7JNdFWOb zHGf){UY68_9XKme_FiMv>?O|E%*2VC(AClSYreckU-6IQvHdy_xOJ)8T*^k5vel(* z#y-Dh)^U%_q6<4c;?;$HWgqxzxCf>9ifD7J8j68xfgF9MzaBdxS6^k*OPlV}BD}OH zFD+6&VcXXK8XbG5KG=IY2R2`J^G#>N_hKi>L5wWKda&eG#z;o3p-3&d8zbW}UG-*n%+MgWv>M(u2XAgGUI66~- zX!UfwJn+Bo?(CXGkH^#L{NG()yz3LM3Fe3F$lJ|sb;b~n_*$c1IP|jEtwr^iB7b5q z%iQ_mnCEe26MZ@{ze9P}q90J64cBRu-)b#|_^!0AI-^}+vj!qIrF;Qm9}I^b*}uVU zUJ-DX?Q$Qtm1MbHkmW9q@oBZY(-R=J#`QkoAs$(~_g5lV-S{b(y}S* delta 4576 zcmV<65g+dFB)24xfqxv!ZW~AP&adcAmN!w3sNFab3@l{X-Vhrrf+=}{y$DURNwqT% zVrD2=5qwztmir>t{g?fde92Znr(ZMELrS)T1h64;rmMQUs=D4ynk~p%R91_lMLtKP z1BAZIQ!+?#_K0Rxk(;md!$)Ys{wy~0isl&~Ty3hnpg3{ItAFQ@(7F7?htF|ER`8XS zEn6Ooyr*$w9ZMx+3t0qb%2W?xPb=AEDP|4$~v?RgC9Pd;szI5t@n* zduUbQ1zAW?gtG-&Zc4t8f=FBy$;tpO`55*t-!ouXvi$xMuTgcoCWC~|Ld!88!*XuZ zlvJ=~HPBlKAAjxNTfxQVV+<2@PKA=oY9#L0V#AA>K_%k@cS)S23PDTY;7o;Rv zg*n3*K!Rr^acTVJpOFSaHqhV<7Zpg$C@&VI7~wR}R-XW+ zgR_z%=X8JWtnoN!d>JfTe8Y9z4w0NA|EB!Nx{{prf@=Yk@g5 z#lxtF;}eOTedq902!C3*I5Dd;kl3}frksX+Sa|KK+CAi0QVLo8& zKVcZ6FqfR^Zw$7|*Ljk!m^zRQ_YfH7fyrVGfPWUa!iSi-zk{y3C6;4h_8>h4?NZ}m3%(I*>K+g%)vMET{o)rYr z5J0-}a#iG;b^Bp|{uUU+7Qx3l3bzzkNJa{rn8v6H+uy}d`jZ4d&Tg6Tw1JG6iZLC9 zMt^0gorRZL%Q4XgC5cUK**cpAia-FcsV2FWq$F|Aw{1vX@DXgRlAh%z6C7;k(uw}I zha`wNSQ!SE!S>;}WhF>Us;Muv@1=1TTNUxsykw?R9o42IoRR7#FMc#$E1|s1dP8CSM~w2Y$9M$3ui_M1Op{ z+!6>xA^lOV6I!`U9IY`89O`y;qA{V7Nn^xtvE!CXRjtF7Sk-R$IZP`nD;%MfL6&Di z#ujwBBn7B+=P5r6;KG0tLz#Ic+M5IKtzFFu1j=!)5hxAL5DcCcm@Ys-q@#EPK0yGh zO>PY%3=4-DXqy-eeQX5Lk^C8+{|IIWoUSy*?up@%9er#q2~4t>v@4 z%*aiQE_tTS6*v($JiKE9D@F{C%aX3RX`Chb{70$2m*DA+wCHu~7mWpREK+wfEXc+( zIDwd+loV%?MqLfpW_^;Cz<(vrdXI3~jM1P*4Q|;}giAdGkhW~e2I%0&NExv6Bz6w` zDS%P8gBO-eaxY}Q0hcWu@c?%;qidGl=<>O2G~E9XXhfWhYBYoAZ!y!LUC#RpQlyl* zoJt{CE@$gn6lBO*HLQtY$RmtWJ=%!BfnWuYipW@pi)9Qlu3Q-?Qh%Vr;kEp7ng8jK zX)94=s9w%&;g=Y1#4ML}wlyp>R>l<;MG}&-*sZwjRszrUSfvFdL%KL#Yu9LJvDU4Y z4wNaC)@6E@7cbuAH0w4`$7-DL{#Gl{9){`Xk?9svE8&7N`!bXp^5{2V$0}7JHfa9} z)1*VqSRhD(aMco_vVUcKGZ~or=x>pw<2xKSwqi$Vfk41|)zj0nav| z%d1=Nb;!(2e9}V#a|WxlPM!boLyOv{>={ZJ&Zw0l1|O3qxM;CG4d-71nl@tLr6LB@1e1)a^7QkIn+0QOuJ+ z^)a(aY~_$^u4=5_H~7cy9(Ew<0^61%Lhw4^M`N$372W08m{1<6O{y z>fu*>e)h&9nvM>l>8wb0a&a&zSY#Y^Sh!}g2zUDH`}dka@a5n7%S3+?#i?`fviU>j z>#2m*GA&4a8uJF`Q!mYj@6|)iu)E2VBe7)|$D#PbJvQon2ls5S@^tuoJp6|LbD5=I z9eA%M-hY#t7}o2C$EWoi)+sdv<8+6Sdfg64c-Yt(=87m^ITv-Q?gSoZ7K@OUuw|PR zBAjR?u#XpuOP-Bqk^7(8;2L0t;{yw!bbq?e6hHBxu|!m79*&85G8&G_)KD+_Q>}i$ z{FoBEhmMs9=nCNI6Glk*AnuT}Q@dU!$k-8W*@& z-3fs{$rDy*R>NN5Y{d+wcnK=tVEheNx(8qLKZj$}Q-N0(YRrWkbRkDw$YJd7H_X=F zBY(1J5$^Dd1SR0T5_L`uDMuznv~gDh#X_~BJos9FJharO`Y4B9m~a;c;DrHsVSw@t zo1Vj`nAj0UVK^JbOX|W{ORhAQVZEC@geb?@SYI>iwVt|1D`}nnR(sgPmfOTv?%mP< zerb!MyzwBq!;{0|w~sAO(vnrcM1f--!haX^gRu3zE=PMr55#%8v5e zn#@x3ZbImoVKLm1upZ?>+=8ajd*uMC(AyyLaYE@K3d&}f+h<~N!pzh7E^=?I{7Cqlw)l?cr^%Ns(?U5*ZBDUY-hu@4)XmJAZ)x zW3AfG4xSv@ksOCR8wVc0>#aa*6*}#Rfh27j_PTQru~);z$ycjW z>X79=_Uftv=SY`-R)eeLiC~)@>qJ&fDE89qT3Em&{p++(#?2&@X={|NSGMHTFbRg)v450)Z_c<1 z-<;G`Z6U21*87dx(IIlVSr?>R=D+7HNXMcBX`irOiyjFhP!pM`wnY0C`^$u`dmO}+ z_n(-Idk9zHzA$NVnMO#8wK+pbJ{SA`(PwqVdm1|ZAl8gh|X@ACDm8LAH=e2@&%2GaNB>yze6T?|OZB0oA^7Y;_I#QD@ z?x0M#>6}V99A^t|@clH3=A50PHV>L&Ema$Iquml|%^sCF*{9GhaNKC>6#7G^d%C;a-;lK^g5!3WxPWI>KL>(}7Cc}waTIBqa^ z+r+vBU0qe#CJE(e`qwP(A>SW9oC})DISnNm;Z1w+FsFe*%;qSshpDbVT%|m;{LiUS z{#g~Zl|ZQ0t!Z_ecz@Q|OHq_kZvWJ5UcpcMJyh%zC5GdJE-Mcd`J;Nbef2IOOOV3q zs!K78yDIx|{(&v)?fi=nj(8u#SJFK)Q6UZ8l!r3yVxJzXdSP_~H%HP^(~4QAgRP~Y zrJc<3Hl5|cf=JlKBy<&@Pl3Um;!=-RDzHyR+Szd*CY%@antzH~P^7N?l$|_WoqyDJ z9#gaa9y)rodH9XA_PkK(`B;G$e_ykkXJx0OKNCbvp$n zu81xk#`?xM{OH%8e`#^EwkhhH%@wjd@wn4_>4;2t=ORyC_vm!wSB)Q{a*6(;Ly^s4L{_mg zps6b<4Ux%VTma(^iw$6GjdW-nRayOS3qC$Yx=$`ECKC7!r2%VSaVJ${x`~~tL=-f= z>k#MX#?=So?!$TDo%$R2A-HABYR~#&7AaL>Fyno^PgtetfmXSjF>!BFC&2m2N z>m`etX}s&JDrfg=W^Av^_4E9DXuG6;?d~6c70mvNsKhM? z1WHVC9yVV{$HmboJ7OSewj#y_(U=QRo@+SKA>PC&#A&U1f0vIMyuOJO6i#X#W(l{Y z!@3IhyO2Fx%I~Ir&9`im=9^G0xJh96j7@FTEq^@$;p?;a?Jv4>J+#wk`Yx7t5mwtz zj=XHH8Jq5*Yg~NsWjX#r5kh@8+Dqkim44}8-*4zp;bQN>Y;iE)gY7}8?YTp&F%w@y9L|;Crkly%_hfpYkWX`!uO1fa!N}3)kH01B4HMT*3_{AGzdrc4^j33T8UGb9a3z`N1lF zzp}{g1>0cBHb$4+Z}sCNmUjCVc$+RIM{?PKaCmM0w88cTx{YyhIoxrClcdiDVukrt z--@34(Zr9C7*gVLTom}$4TK$yY*}dPP^#~9%l=qf3y0`YS}?3V+cJo;XFzxnqE@bc z^7S6vk`Tg}t_S&J!`l>D;(tC8p0Q5Osqrh4)0RuNO~sh)eoa!oQH_qqHC11hES9s2 KtN#a5+tBxaeC>Mx diff --git a/previews/PR187/search_index.js b/previews/PR187/search_index.js index 5ee09c02..62803a79 100644 --- a/previews/PR187/search_index.js +++ b/previews/PR187/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"lib/tensors/#Tensors","page":"Tensors","title":"Tensors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/tensors/#Type-hierarchy","page":"Tensors","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The abstract supertype of all tensors in TensorKit is given by AbstractTensorMap:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"AbstractTensorMap","category":"page"},{"location":"lib/tensors/#TensorKit.AbstractTensorMap","page":"Tensors","title":"TensorKit.AbstractTensorMap","text":"abstract type AbstractTensorMap{T<:Number, S<:IndexSpace, N₁, N₂} end\n\nAbstract supertype of all tensor maps, i.e. linear maps between tensor products of vector spaces of type S<:IndexSpace, with element type T. An AbstractTensorMap maps from an input space of type ProductSpace{S, N₂} to an output space of type ProductSpace{S, N₁}.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The following concrete subtypes are provided within the TensorKit library:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap\nDiagonalTensorMap\nAdjointTensorMap\nBraidingTensor","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap","page":"Tensors","title":"TensorKit.TensorMap","text":"struct TensorMap{T, S<:IndexSpace, N₁, N₂, A<:DenseVector{T}} <: AbstractTensorMap{T, S, N₁, N₂}\n\nSpecific subtype of AbstractTensorMap for representing tensor maps (morphisms in a tensor category), where the data is stored in a dense vector.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.DiagonalTensorMap","page":"Tensors","title":"TensorKit.DiagonalTensorMap","text":"DiagonalTensorMap{T}(undef, domain::S) where {T,S<:IndexSpace}\n# expert mode: select storage type `A`\nDiagonalTensorMap{T,S,A}(undef, domain::S) where {T,S<:IndexSpace,A<:DenseVector{T}}\n\nConstruct a DiagonalTensorMap with uninitialized data.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.AdjointTensorMap","page":"Tensors","title":"TensorKit.AdjointTensorMap","text":"struct AdjointTensorMap{T, S, N₁, N₂, TT<:AbstractTensorMap} <: AbstractTensorMap{T, S, N₁, N₂}\n\nSpecific subtype of AbstractTensorMap that is a lazy wrapper for representing the adjoint of an instance of AbstractTensorMap.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.BraidingTensor","page":"Tensors","title":"TensorKit.BraidingTensor","text":"struct BraidingTensor{T,S<:IndexSpace} <: AbstractTensorMap{T, S, 2, 2}\nBraidingTensor(V1::S, V2::S, adjoint::Bool=false) where {S<:IndexSpace}\n\nSpecific subtype of AbstractTensorMap for representing the braiding tensor that braids the first input over the second input; its inverse can be obtained as the adjoint.\n\nIt holds that domain(BraidingTensor(V1, V2)) == V1 ⊗ V2 and codomain(BraidingTensor(V1, V2)) == V2 ⊗ V1.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Of those, TensorMap provides the generic instantiation of our tensor concept. It supports various constructors, which are discussed in the next subsection.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Furthermore, some aliases are provided for convenience:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"AbstractTensor\nTensor","category":"page"},{"location":"lib/tensors/#TensorKit.AbstractTensor","page":"Tensors","title":"TensorKit.AbstractTensor","text":"AbstractTensor{T,S,N} = AbstractTensorMap{T,S,N,0}\n\nAbstract supertype of all tensors, i.e. elements in the tensor product space of type ProductSpace{S, N}, with element type T.\n\nAn AbstractTensor{T, S, N} is actually a special case AbstractTensorMap{T, S, N, 0}, i.e. a tensor map with only non-trivial output spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.Tensor","page":"Tensors","title":"TensorKit.Tensor","text":"Tensor{T, S, N, A<:DenseVector{T}} = TensorMap{T, S, N, 0, A}\n\nSpecific subtype of AbstractTensor for representing tensors whose data is stored in a dense vector.\n\nA Tensor{T, S, N, A} is actually a special case TensorMap{T, S, N, 0, A}, i.e. a tensor map with only a non-trivial output space.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorMap-constructors","page":"Tensors","title":"TensorMap constructors","text":"","category":"section"},{"location":"lib/tensors/#General-constructors","page":"Tensors","title":"General constructors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"A TensorMap with undefined data can be constructed by specifying its domain and codomain:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap{T}(::UndefInitializer, V::TensorMapSpace{S,N₁,N₂}) where {T,S,N₁,N₂}","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{UndefInitializer, HomSpace{S, ProductSpace{S, N₁}, ProductSpace{S, N₂}}}} where {T, S, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap{T}(undef, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂})\n where {T,S,N₁,N₂}\nTensorMap{T}(undef, codomain ← domain)\nTensorMap{T}(undef, domain → codomain)\n# expert mode: select storage type `A`\nTensorMap{T,S,N₁,N₂,A}(undef, codomain ← domain)\nTensorMap{T,S,N₁,N₂,A}(undef, domain → domain)\n\nConstruct a TensorMap with uninitialized data.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The resulting object can then be filled with data using the setindex! method as discussed below, using functions such as VectorInterface.zerovector!, rand! or fill!, or it can be used as an output argument in one of the many methods that accept output arguments, or in an @tensor output[...] = ... expression.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Alternatively, a TensorMap can be constructed by specifying its data, codmain and domain in one of the following ways:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, V::TensorMapSpace{S,N₁,N₂}) where {S,N₁,N₂}\nTensorMap(data::AbstractArray, V::TensorMapSpace{S,N₁,N₂}; tol) where {S<:IndexSpace,N₁,N₂}","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{AbstractDict{<:Sector, <:AbstractMatrix}, HomSpace{S, ProductSpace{S, N₁}, ProductSpace{S, N₂}}}} where {S, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S<:ElementarySpace,N₁,N₂}\nTensorMap(data, codomain ← domain)\nTensorMap(data, domain → codomain)\n\nConstruct a TensorMap by explicitly specifying its block data.\n\nArguments\n\ndata::AbstractDict{<:Sector,<:AbstractMatrix}: dictionary containing the block data for each coupled sector c as a matrix of size (blockdim(codomain, c), blockdim(domain, c)).\ncodomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.\ndomain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.\n\nAlternatively, the domain and codomain can be specified by passing a HomSpace using the syntax codomain ← domain or domain → codomain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{AbstractArray, TensorMapSpace{S, N₁, N₂}}} where {S<:ElementarySpace, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap(data::AbstractArray, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂};\n tol=sqrt(eps(real(float(eltype(data)))))) where {S<:ElementarySpace,N₁,N₂}\nTensorMap(data, codomain ← domain; tol=sqrt(eps(real(float(eltype(data))))))\nTensorMap(data, domain → codomain; tol=sqrt(eps(real(float(eltype(data))))))\n\nConstruct a TensorMap from a plain multidimensional array.\n\nArguments\n\ndata::DenseArray: tensor data as a plain array.\ncodomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.\ndomain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.\ntol=sqrt(eps(real(float(eltype(data)))))::Float64: \n\nHere, data can be specified in three ways:\n\ndata can be a DenseVector of length dim(codomain ← domain); in that case it represents the actual independent entries of the tensor map. An instance will be created that directly references data.\ndata can be an AbstractMatrix of size (dim(codomain), dim(domain))\ndata can be an AbstractArray of rank N₁ + N₂ with a size matching that of the domain and codomain spaces, i.e. size(data) == (dims(codomain)..., dims(domain)...)\n\nIn case 2 and 3, the TensorMap constructor will reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t), up to an error specified by tol. For the case where sectortype(S) == Trivial and data isa DenseArray, the data array is simply reshaped into a vector and used as in case 1 so that the memory will still be shared. In other cases, new memory will be allocated.\n\nNote that in the case of N₁ + N₂ = 1, case 3 also amounts to data being a vector, whereas when N₁ + N₂ == 2, case 2 and case 3 both require data to be a matrix. Such ambiguous cases are resolved by checking the size of data in an attempt to support all possible cases.\n\nnote: Note\nThis constructor for case 2 and 3 only works for sectortype values for which conversion to a plain array is possible, and only in the case where the data actually respects the specified symmetry structure, up to a tolerance tol.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Finally, we also support the following Array-like constructors","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"zeros(::Type, V::TensorMapSpace)\nones(::Type, V::TensorMapSpace)\nrand(::Type, V::TensorMapSpace)\nrandn(::Type, V::TensorMapSpace)\nRandom.randexp(::Type, V::TensorMapSpace)","category":"page"},{"location":"lib/tensors/#Base.zeros-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.zeros","text":"zeros([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}\nzeros([T=Float64,], codomain ← domain)\n\nCreate a TensorMap with element type T, of all zeros with spaces specified by codomain and domain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.ones-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.ones","text":"ones([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}\nones([T=Float64,], codomain ← domain)\n\nCreate a TensorMap with element type T, of all ones with spaces specified by codomain and domain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.rand-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.rand","text":"rand([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrand([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by rand.\n\nSee also (rand)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.randn-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.randn","text":"randn([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrandn([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by randn.\n\nSee also (randn)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Random.randexp-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Random.randexp","text":"randexp([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrandexp([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by randexp.\n\nSee also (randexp)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"as well as a similar constructor","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.similar(::AbstractTensorMap, args...)","category":"page"},{"location":"lib/tensors/#Base.similar-Tuple{AbstractTensorMap, Vararg{Any}}","page":"Tensors","title":"Base.similar","text":"similar(t::AbstractTensorMap, [AorT=storagetype(t)], [V=space(t)])\nsimilar(t::AbstractTensorMap, [AorT=storagetype(t)], codomain, domain)\n\nCreates an uninitialized mutable tensor with the given scalar or storagetype AorT and structure V or codomain ← domain, based on the source tensormap. The second and third arguments are both optional, defaulting to the given tensor's storagetype and space. The structure may be specified either as a single HomSpace argument or as codomain and domain.\n\nBy default, this will result in TensorMap{T}(undef, V) when custom objects do not specialize this method.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Specific-constructors","page":"Tensors","title":"Specific constructors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Additionally, the following methods can be used to construct specific TensorMap instances.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"id\nisomorphism\nunitary\nisometry","category":"page"},{"location":"lib/tensors/#TensorKit.id","page":"Tensors","title":"TensorKit.id","text":"id([T::Type=Float64,] V::TensorSpace) -> TensorMap\n\nConstruct the identity endomorphism on space V, i.e. return a t::TensorMap with domain(t) == codomain(t) == V, where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.isomorphism","page":"Tensors","title":"TensorKit.isomorphism","text":"isomorphism([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nisomorphism([T::Type=Float64,] codomain ← domain) -> TensorMap\nisomorphism([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific isomorphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, an error will be thrown.\n\nnote: Note\nThere is no canonical choice for a specific isomorphism, but the current choice is such that isomorphism(cod, dom) == inv(isomorphism(dom, cod)).\n\nSee also unitary when InnerProductStyle(cod) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.unitary","page":"Tensors","title":"TensorKit.unitary","text":"unitary([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nunitary([T::Type=Float64,] codomain ← domain) -> TensorMap\nunitary([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific unitary morphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, or the spacetype does not have a Euclidean inner product, an error will be thrown.\n\nnote: Note\nThere is no canonical choice for a specific unitary, but the current choice is such that unitary(cod, dom) == inv(unitary(dom, cod)) = adjoint(unitary(dom, cod)).\n\nSee also isomorphism and isometry.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.isometry","page":"Tensors","title":"TensorKit.isometry","text":"isometry([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nisometry([T::Type=Float64,] codomain ← domain) -> TensorMap\nisometry([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific isometry between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. The isometry t then satisfies t' * t = id(domain) and (t * t')^2 = t * t'. If the spaces do not allow for such an isometric inclusion, an error will be thrown.\n\nSee also isomorphism and unitary.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#AbstractTensorMap-properties-and-data-access","page":"Tensors","title":"AbstractTensorMap properties and data access","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The following methods exist to obtain type information:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.eltype(::Type{<:AbstractTensorMap{T}}) where {T}\nspacetype(::Type{<:AbstractTensorMap{<:Any,S}}) where {S}\nsectortype(::Type{TT}) where {TT<:AbstractTensorMap}\nfield(::Type{TT}) where {TT<:AbstractTensorMap}\nstoragetype","category":"page"},{"location":"lib/tensors/#Base.eltype-Union{Tuple{Type{<:AbstractTensorMap{T}}}, Tuple{T}} where T","page":"Tensors","title":"Base.eltype","text":"eltype(::AbstractTensorMap) -> Type{T}\neltype(::Type{<:AbstractTensorMap}) -> Type{T}\n\nReturn the scalar or element type T of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.spacetype-Union{Tuple{Type{<:AbstractTensorMap{<:Any, S}}}, Tuple{S}} where S","page":"Tensors","title":"TensorKit.spacetype","text":"spacetype(::AbstractTensorMap) -> Type{S<:IndexSpace}\nspacetype(::Type{<:AbstractTensorMap}) -> Type{S<:IndexSpace}\n\nReturn the type of the elementary space S of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.sectortype-Union{Tuple{Type{TT}}, Tuple{TT}} where TT<:AbstractTensorMap","page":"Tensors","title":"TensorKit.sectortype","text":"sectortype(::AbstractTensorMap) -> Type{I<:Sector}\nsectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}\n\nReturn the type of sector I of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.field-Union{Tuple{Type{TT}}, Tuple{TT}} where TT<:AbstractTensorMap","page":"Tensors","title":"TensorKit.field","text":"field(::AbstractTensorMap) -> Type{𝔽<:Field}\nfield(::Type{<:AbstractTensorMap}) -> Type{𝔽<:Field}\n\nReturn the type of field 𝔽 of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.storagetype","page":"Tensors","title":"TensorKit.storagetype","text":"storagetype(t::AbstractTensorMap) -> Type{A<:AbstractVector}\nstoragetype(T::Type{<:AbstractTensorMap}) -> Type{A<:AbstractVector}\n\nReturn the type of vector that stores the data of a tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To obtain information about the indices, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"space(::AbstractTensorMap, ::Int)\ndomain\ncodomain\nnumin\nnumout\nnumind\ncodomainind\ndomainind\nallind","category":"page"},{"location":"lib/tensors/#TensorKit.space-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKit.space","text":"space(t::AbstractTensorMap{T,S,N₁,N₂}) -> HomSpace{S,N₁,N₂}\nspace(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nThe index information of a tensor, i.e. the HomSpace of its domain and codomain. If i is specified, return the i-th index space.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.domain","page":"Tensors","title":"TensorKit.domain","text":"domain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₂}\ndomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nReturn the domain of a tensor, i.e. the product space of the input spaces. If i is specified, return the i-th input space. Implementations should provide domain(t).\n\nSee also codomain and space.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.codomain","page":"Tensors","title":"TensorKit.codomain","text":"codomain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₁}\ncodomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nReturn the codomain of a tensor, i.e. the product space of the output spaces. If i is specified, return the i-th output space. Implementations should provide codomain(t).\n\nSee also domain and space.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numin","page":"Tensors","title":"TensorKit.numin","text":"numin(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int\n\nReturn the number of input spaces of a tensor. This is equivalent to the number of spaces in the domain of that tensor.\n\nSee also numout and numind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numout","page":"Tensors","title":"TensorKit.numout","text":"numout(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int\n\nReturn the number of output spaces of a tensor. This is equivalent to the number of spaces in the codomain of that tensor.\n\nSee also numin and numind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numind","page":"Tensors","title":"TensorKit.numind","text":"numind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Int\n\nReturn the total number of input and output spaces of a tensor. This is equivalent to the total number of spaces in the domain and codomain of that tensor.\n\nSee also numout and numin.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.codomainind","page":"Tensors","title":"TensorKit.codomainind","text":"codomainind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of the codomain of a tensor.\n\nSee also domainind and allind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.domainind","page":"Tensors","title":"TensorKit.domainind","text":"domainind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of the domain of a tensor.\n\nSee also codomainind and allind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.allind","page":"Tensors","title":"TensorKit.allind","text":"allind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of a tensor, i.e. the indices of its domain and codomain.\n\nSee also codomainind and domainind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"In TensorMap instances, all data is gathered in a single AbstractVector, which has an internal structure into blocks associated to total coupled charge, within which live subblocks associated with the different possible fusion-splitting tree pairs.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To obtain information about the structure of the data, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"fusionblockstructure(::AbstractTensorMap)\ndim(::AbstractTensorMap)\nblocksectors(::AbstractTensorMap)\nhasblock(::AbstractTensorMap, ::Sector)\nfusiontrees(t::AbstractTensorMap)","category":"page"},{"location":"lib/tensors/#TensorKit.fusionblockstructure-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.fusionblockstructure","text":"fusionblockstructure(t::AbstractTensorMap) -> TensorStructure\n\nReturn the necessary structure information to decompose a tensor in blocks labeled by coupled sectors and in subblocks labeled by a splitting-fusion tree couple.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKitSectors.dim-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKitSectors.dim","text":"dim(t::AbstractTensorMap) -> Int\n\nThe total number of free parameters of a tensor, discounting the entries that are fixed by symmetry. This is also the dimension of the HomSpace on which the TensorMap is defined.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.blocksectors-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.blocksectors","text":"blocksectors(t::AbstractTensorMap)\n\nReturn an iterator over all coupled sectors of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.hasblock-Tuple{AbstractTensorMap, Sector}","page":"Tensors","title":"TensorKit.hasblock","text":"hasblock(t::AbstractTensorMap, c::Sector) -> Bool\n\nVerify whether a tensor has a block corresponding to a coupled sector c.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.fusiontrees-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.fusiontrees","text":"fusiontrees(t::AbstractTensorMap)\n\nReturn an iterator over all splitting - fusion tree pairs of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Data can be accessed (and modified) in a number of ways. To access the full matrix block associated with the coupled charges, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"block\nblocks","category":"page"},{"location":"lib/tensors/#TensorKit.block","page":"Tensors","title":"TensorKit.block","text":"block(t::AbstractTensorMap, c::Sector)\n\nReturn the matrix block of a tensor corresponding to a coupled sector c.\n\nSee also blocks, blocksectors, blockdim and hasblock.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.blocks","page":"Tensors","title":"TensorKit.blocks","text":"blocks(t::AbstractTensorMap)\n\nReturn an iterator over all blocks of a tensor, i.e. all coupled sectors and their corresponding matrix blocks.\n\nSee also block, blocksectors, blockdim and hasblock.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To access the data associated with a specific fusion tree pair, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::TensorMap{T,S,N₁,N₂}, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}\nBase.setindex!(::TensorMap{T,S,N₁,N₂}, ::Any, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}","category":"page"},{"location":"lib/tensors/#Base.getindex-Union{Tuple{I}, Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{TensorMap{T, S, N₁, N₂, A} where A<:DenseVector{T}, FusionTree{I, N₁}, FusionTree{I, N₂}}} where {T, S, N₁, N₂, I<:Sector}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::TensorMap{T,S,N₁,N₂,I},\n f₁::FusionTree{I,N₁},\n f₂::FusionTree{I,N₂}) where {T,SN₁,N₂,I<:Sector}\n -> StridedViews.StridedView\nt[f₁, f₂]\n\nReturn a view into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). In particular, if f₁.coupled == f₂.coupled == c, then a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) is returned which represents the slice of block(t, c) whose row indices correspond to f₁.uncoupled and column indices correspond to f₂.uncoupled.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.setindex!-Union{Tuple{I}, Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{TensorMap{T, S, N₁, N₂, A} where A<:DenseVector{T}, Any, FusionTree{I, N₁}, FusionTree{I, N₂}}} where {T, S, N₁, N₂, I<:Sector}","page":"Tensors","title":"Base.setindex!","text":"Base.setindex!(t::TensorMap{T,S,N₁,N₂,I},\n v,\n f₁::FusionTree{I,N₁},\n f₂::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}\nt[f₁, f₂] = v\n\nCopies v into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). Here, v can be any object that can be copied into a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) using Base.copy!.\n\nSee also Base.getindex(::TensorMap{T,S,N₁,N₂,I<:Sector}, ::FusionTree{I<:Sector,N₁}, ::FusionTree{I<:Sector,N₂})\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"For a tensor t with FusionType(sectortype(t)) isa UniqeFuison, fusion trees are completely determined by the outcoming sectors, and the data can be accessed in a more straightforward way:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::TensorMap, ::Tuple{I,Vararg{I}}) where {I<:Sector}","category":"page"},{"location":"lib/tensors/#Base.getindex-Union{Tuple{I}, Tuple{TensorMap, Tuple{I, Vararg{I}}}} where I<:Sector","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::TensorMap\n sectors::NTuple{N₁+N₂,I}) where {N₁,N₂,I<:Sector} \n -> StridedViews.StridedView\nt[sectors]\n\nReturn a view into the data slice of t corresponding to the splitting - fusion tree pair with combined uncoupled charges sectors. In particular, if sectors == (s₁..., s₂...) where s₁ and s₂ correspond to the coupled charges in the codomain and domain respectively, then a StridedViews.StridedView of size (dims(codomain(t), s₁)..., dims(domain(t), s₂)) is returned.\n\nThis method is only available for the case where FusionStyle(I) isa UniqueFusion, since it assumes a uniquely defined coupled charge.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"For tensor t with sectortype(t) == Trivial, the data can be accessed and manipulated directly as multidimensional arrays:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::AbstractTensorMap)\nBase.getindex(::AbstractTensorMap, ::Vararg{SliceIndex})\nBase.setindex!(::AbstractTensorMap, ::Any, ::Vararg{SliceIndex})","category":"page"},{"location":"lib/tensors/#Base.getindex-Tuple{AbstractTensorMap}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::AbstractTensorMap)\nt[]\n\nReturn a view into the data of t as a StridedViews.StridedView of size (dims(codomain(t))..., dims(domain(t))...).\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.getindex-Tuple{AbstractTensorMap, Vararg{Union{Colon, AbstractRange{<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::AbstractTensorMap, indices::Vararg{Int})\nt[indices]\n\nReturn a view into the data slice of t corresponding to indices, by slicing the StridedViews.StridedView into the full data array.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.setindex!-Tuple{AbstractTensorMap, Any, Vararg{Union{Colon, AbstractRange{<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}}","page":"Tensors","title":"Base.setindex!","text":"Base.setindex!(t::AbstractTensorMap, v, indices::Vararg{Int})\nt[indices] = v\n\nAssigns v to the data slice of t corresponding to indices.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#AbstractTensorMap-operations","page":"Tensors","title":"AbstractTensorMap operations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The operations that can be performed on an AbstractTensorMap can be organized into the following categories:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"vector operations: these do not change the space or index strucure of a tensor and can be straightforwardly implemented on on the full data. All the methods described in VectorInterface.jl are supported. For compatibility reasons, we also provide implementations for equivalent methods from LinearAlgebra.jl, such as axpy!, axpby!.\nindex manipulations: these change (permute) the index structure of a tensor, which affects the data in a way that is fully determined by the categorical data of the sectortype of the tensor.\n(planar) contractions and (planar) traces (i.e., contractions with identity tensors). Tensor contractions correspond to a combination of some index manipulations followed by a composition or multiplication of the tensors in their role as linear maps. Tensor contractions are however of such important and frequency that they require a dedicated implementation.\ntensor factorisations, which relies on their identification of tensors with linear maps between tensor spaces. The factorisations are applied as ordinary matrix factorisations to the matrix blocks associated with the coupled charges.","category":"page"},{"location":"lib/tensors/#Index-manipulations","page":"Tensors","title":"Index manipulations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"A general index manipulation of a TensorMap object can be built up by considering some transformation of the fusion trees, along with a permutation of the stored data. They come in three flavours, which are either of the type transform(!) which are exported, or of the type add_transform!, for additional expert-mode options that allows for addition and scaling, as well as the selection of a custom backend.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"permute(::AbstractTensorMap, ::Index2Tuple{N₁,N₂}; ::Bool) where {N₁,N₂}\nbraid(::AbstractTensorMap, ::Index2Tuple, ::IndexTuple; ::Bool)\ntranspose(::AbstractTensorMap, ::Index2Tuple; ::Bool)\nrepartition(::AbstractTensorMap, ::Int, ::Int; ::Bool)\ntwist(::AbstractTensorMap, ::Int; ::Bool)","category":"page"},{"location":"lib/tensors/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {N₁, N₂}","page":"Tensors","title":"TensorKit.permute","text":"permute(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;\n copy::Bool=false)\n -> tdst::TensorMap\n\nReturn 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.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo permute into an existing destination, see permute! and add_permute!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.braid-Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}, NTuple{N, Int64} where N}","page":"Tensors","title":"TensorKit.braid","text":"braid(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple, levels::IndexTuple;\n copy::Bool = false)\n -> tdst::TensorMap\n\nReturn 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.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo braid into an existing destination, see braid! and add_braid!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.transpose-Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}}","page":"Tensors","title":"Base.transpose","text":"transpose(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;\n copy::Bool=false)\n -> tdst::TensorMap\n\nReturn 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))...).\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo permute into an existing destination, see permute! and add_permute!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.repartition-Tuple{AbstractTensorMap, Int64, Int64}","page":"Tensors","title":"TensorKit.repartition","text":"repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}\n -> tdst::AbstractTensorMap{S,N₁,N₂}\n\nReturn tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo repartition into an existing destination, see repartition!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKitSectors.twist-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKitSectors.twist","text":"twist(tsrc::AbstractTensorMap, i::Int; inv::Bool=false) -> tdst\ntwist(tsrc::AbstractTensorMap, is; inv::Bool=false) -> tdst\n\nApply a twist to the ith index of tsrc and return the result as a new tensor. If inv=true, use the inverse twist.\n\nSee twist! for storing the result in place.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"permute!(::AbstractTensorMap, ::AbstractTensorMap, ::Index2Tuple)\nbraid!\ntranspose!\nrepartition!\ntwist!","category":"page"},{"location":"lib/tensors/#Base.permute!-Tuple{AbstractTensorMap, AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}}","page":"Tensors","title":"Base.permute!","text":"permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple)\n -> tdst\n\nWrite into tdst the result of permuting the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively.\n\nSee permute for creating a new tensor and add_permute! for a more general version.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.braid!","page":"Tensors","title":"TensorKit.braid!","text":"braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple, levels::Tuple)\n -> tdst\n\nWrite into tdst the result of 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.\n\nSee braid for creating a new tensor and add_braid! for a more general version.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.transpose!","page":"Tensors","title":"LinearAlgebra.transpose!","text":"transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple)\n -> tdst\n\nWrite into tdst the result of 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))...).\n\nSee transpose for creating a new tensor and add_transpose! for a more general version.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.repartition!","page":"Tensors","title":"TensorKit.repartition!","text":"repartition!(tdst::AbstractTensorMap{S}, tsrc::AbstractTensorMap{S}) where {S} -> tdst\n\nWrite into tdst the result of repartitioning the indices of tsrc. This is just a special case of a transposition that only changes the number of in- and outgoing indices.\n\nSee repartition for creating a new tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.twist!","page":"Tensors","title":"TensorKit.twist!","text":"twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) -> t\ntwist!(t::AbstractTensorMap, is; inv::Bool=false) -> t\n\nApply a twist to the ith index of t, or all indices in is, storing the result in t. If inv=true, use the inverse twist.\n\nSee twist for creating a new tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorKit.add_permute!\nTensorKit.add_braid!\nTensorKit.add_transpose!","category":"page"},{"location":"lib/tensors/#TensorKit.add_permute!","page":"Tensors","title":"TensorKit.add_permute!","text":"add_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂).\n\nSee also permute, permute!, add_braid!, add_transpose!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.add_braid!","page":"Tensors","title":"TensorKit.add_braid!","text":"add_braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n levels::IndexTuple, α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after braiding the indices of tsrc according to (p₁, p₂) and levels.\n\nSee also braid, braid!, add_permute!, add_transpose!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.add_transpose!","page":"Tensors","title":"TensorKit.add_transpose!","text":"add_transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after transposing the indices of tsrc according to (p₁, p₂).\n\nSee also transpose, transpose!, add_permute!, add_braid!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#Tensor-map-composition,-traces,-contractions-and-tensor-products","page":"Tensors","title":"Tensor map composition, traces, contractions and tensor products","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"compose(::AbstractTensorMap, ::AbstractTensorMap)\ntrace_permute!\ncontract!\n⊗(::AbstractTensorMap, ::AbstractTensorMap)","category":"page"},{"location":"lib/tensors/#TensorKit.compose-Tuple{AbstractTensorMap, AbstractTensorMap}","page":"Tensors","title":"TensorKit.compose","text":"compose(t1::AbstractTensorMap, t2::AbstractTensorMap) -> AbstractTensorMap\n\nReturn the AbstractTensorMap that implements the composition of the two tensor maps t1 and t2.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.trace_permute!","page":"Tensors","title":"TensorKit.trace_permute!","text":"trace_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple, (q₁, q₂)::Index2Tuple,\n α::Number, β::Number, backend=TO.DefaultBackend())\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂) and furthermore tracing the indices in q₁ and q₂.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.contract!","page":"Tensors","title":"TensorKit.contract!","text":"contract!(C::AbstractTensorMap,\n A::AbstractTensorMap, (oindA, cindA)::Index2Tuple,\n B::AbstractTensorMap, (cindB, oindB)::Index2Tuple,\n (p₁, p₂)::Index2Tuple,\n α::Number, β::Number,\n backend, allocator)\n\nReturn the updated C, which is the result of adding α * A * B to C after permuting the indices of A and B according to (oindA, cindA) and (cindB, oindB) respectively.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKitSectors.:⊗-Tuple{AbstractTensorMap, AbstractTensorMap}","page":"Tensors","title":"TensorKitSectors.:⊗","text":"⊗(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap\notimes(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap\n\nCompute the tensor product between two AbstractTensorMap instances, which results in a new TensorMap instance whose codomain is codomain(t1) ⊗ codomain(t2) and whose domain is domain(t1) ⊗ domain(t2).\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorMap-factorizations","page":"Tensors","title":"TensorMap factorizations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The factorisation methods come in two flavors, namely a non-destructive version where you can specify an additional permutation of the domain and codomain indices before the factorisation is performed (provided that sectorstyle(t) has a symmetric braiding) as well as a destructive version The non-destructive methods are given first:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"leftorth\nrightorth\nleftnull\nrightnull\ntsvd\neigh\neig\neigen\nisposdef","category":"page"},{"location":"lib/tensors/#TensorKit.leftorth","page":"Tensors","title":"TensorKit.leftorth","text":"leftorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R\n\nCreate orthonormal basis Q for indices in leftind, and remainder R such that permute(t, (leftind, rightind)) = Q*R.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftorth!(t, alg = QRpos()).\n\nDifferent algorithms are available, namely QR(), QRpos(), SVD() and Polar(). QR() and QRpos() use a standard QR decomposition, producing an upper triangular matrix R. Polar() produces a Hermitian and positive semidefinite R. QRpos() corrects the standard QR decomposition such that the diagonal elements of R are positive. Only QRpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.rightorth","page":"Tensors","title":"TensorKit.rightorth","text":"rightorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q\n\nCreate orthonormal basis Q for indices in rightind, and remainder L such that permute(t, (leftind, rightind)) = L*Q.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightorth!(t, alg = LQpos()).\n\nDifferent algorithms are available, namely LQ(), LQpos(), RQ(), RQpos(), SVD() and Polar(). LQ() and LQpos() produce a lower triangular matrix L and are computed using a QR decomposition of the transpose. RQ() and RQpos() produce an upper triangular remainder L and only works if the total left dimension is smaller than or equal to the total right dimension. LQpos() and RQpos() add an additional correction such that the diagonal elements of L are positive. Polar() produces a Hermitian and positive semidefinite L. Only LQpos(), RQpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.leftnull","page":"Tensors","title":"TensorKit.leftnull","text":"leftnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = QRpos()) -> N\n\nCreate orthonormal basis for the orthogonal complement of the support of the indices in leftind, such that N' * permute(t, (leftind, rightind)) = 0.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftnull!(t, alg = QRpos()).\n\nDifferent algorithms are available, namely QR() (or equivalently, QRpos()), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.rightnull","page":"Tensors","title":"TensorKit.rightnull","text":"rightnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = LQ(),\n atol::Real = 0.0,\n rtol::Real = eps(real(float(one(scalartype(t)))))*iszero(atol)) -> N\n\nCreate orthonormal basis for the orthogonal complement of the support of the indices in rightind, such that permute(t, (leftind, rightind))*N' = 0.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightnull!(t, alg = LQpos()).\n\nDifferent algorithms are available, namely LQ() (or equivalently, LQpos), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.tsvd","page":"Tensors","title":"TensorKit.tsvd","text":"tsvd(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n trunc::TruncationScheme = notrunc(), p::Real = 2, alg::Union{SVD, SDD} = SDD())\n -> U, S, V, ϵ\n\nCompute the (possibly truncated) singular value decomposition such that norm(permute(t, (leftind, rightind)) - U * S * V) ≈ ϵ, where ϵ thus represents the truncation error.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using tsvd!(t, trunc = notrunc(), p = 2).\n\nA truncation parameter trunc can be specified for the new internal dimension, in which case a truncated singular value decomposition will be computed. Choices are:\n\nnotrunc(): no truncation (default);\ntruncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η;\ntruncdim(χ::Int): truncates such that the equivalent total dimension of the internal vector space is no larger than χ;\ntruncspace(V): truncates such that the dimension of the internal vector space is smaller than that of V in any sector.\ntruncbelow(η::Real): truncates such that every singular value is larger then η ;\n\nTruncation options can also be combined using &, i.e. truncbelow(η) & truncdim(χ) will choose the truncation space such that every singular value is larger than η, and the equivalent total dimension of the internal vector space is no larger than χ.\n\nThe method tsvd also returns the truncation error ϵ, computed as the p norm of the singular values that were truncated.\n\nTHe keyword alg can be equal to SVD() or SDD(), corresponding to the underlying LAPACK algorithm that computes the decomposition (_gesvd or _gesdd).\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and tsvd(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.eigh","page":"Tensors","title":"TensorKit.eigh","text":"eigh(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eigh assumes that the linear map is hermitian and D and V tensors with the same scalartype as t. See eig and eigen for non-hermitian tensors. Hermiticity requires that the tensor acts on inner product spaces, and the current implementation requires InnerProductStyle(t) === EuclideanInnerProduct().\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigh!(t). Note that the permuted tensor on which eigh! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nSee also eigen and eig.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.eig","page":"Tensors","title":"TensorKit.eig","text":"eig(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eig assumes that the linear map is not hermitian and returns type stable complex valued D and V tensors for both real and complex valued t. See eigh for hermitian linear maps\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eig!(t). Note that the permuted tensor on which eig! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nAccepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.\n\nSee also eigen and eigh.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.eigen","page":"Tensors","title":"LinearAlgebra.eigen","text":"eigen(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigen!(t). Note that the permuted tensor on which eigen! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nAccepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.\n\nSee also eig and eigh\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.isposdef","page":"Tensors","title":"LinearAlgebra.isposdef","text":"isposdef(t::AbstractTensor, (leftind, rightind)::Index2Tuple) -> ::Bool\n\nTest whether a tensor t is positive definite as linear map from rightind to leftind.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using isposdef!(t). Note that the permuted tensor on which isposdef! is called should have equal domain and codomain, as otherwise it is meaningless.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The corresponding destructive methods have an exclamation mark at the end of their name, and only accept the TensorMap object as well as the method-specific algorithm and keyword arguments.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TODO: document svd truncation types","category":"page"},{"location":"man/tensors/#s_tensors","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"using TensorKit\nusing LinearAlgebra","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This last page explains how to create and manipulate tensors in TensorKit.jl. As this is probably the most important part of the manual, we will also focus more strongly on the usage and interface, and less so on the underlying implementation. The only aspect of the implementation that we will address is the storage of the tensor data, as this is important to know how to create and initialize a tensor, but will in fact also shed light on how some of the methods work.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"As mentioned, all tensors in TensorKit.jl are interpreted as linear maps (morphisms) from a domain (a ProductSpace{S,N₂}) to a codomain (another ProductSpace{S,N₁}), with the same S<:ElementarySpace that labels the type of spaces associated with the individual tensor indices. The overall type for all such tensor maps is AbstractTensorMap{S, N₁, N₂}. Note that we place information about the codomain before that of the domain. Indeed, we have already encountered the constructor for the concrete parametric type TensorMap in the form TensorMap(..., codomain, domain). This convention is opposite to the mathematical notation, e.g. mathrmHom(WV) or fWV, but originates from the fact that a normal matrix is also denoted as having size m × n or is constructed in Julia as Array(..., (m, n)), where the first integer m refers to the codomain being m- dimensional, and the seond integer n to the domain being n-dimensional. This also explains why we have consistently used the symbol W for spaces in the domain and V for spaces in the codomain. A tensor map t(W₁ W_N₂) (V₁ V_N₁) will be created in Julia as TensorMap(..., V1 ⊗ ... ⊗ VN₁, W1 ⊗ ... ⊗ WN2).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, the abstract type AbstractTensor{S,N} is just a synonym for AbstractTensorMap{S,N,0}, i.e. for tensor maps with an empty domain, which is equivalent to the unit of the monoidal category, or thus, the field of scalars 𝕜.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Currently, AbstractTensorMap has two subtypes. TensorMap provides the actual implementation, where the data of the tensor is stored in a DenseArray (more specifically a DenseMatrix as will be explained below). AdjointTensorMap is a simple wrapper type to denote the adjoint of an existing TensorMap object. In the future, additional types could be defined, to deal with sparse data, static data, diagonal data, etc...","category":"page"},{"location":"man/tensors/#ss_tensor_storage","page":"Tensors and the TensorMap type","title":"Storage of tensor data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Before discussion how to construct and initalize a TensorMap{S}, let us discuss what is meant by 'tensor data' and how it can efficiently and compactly be stored. Let us first discuss the case sectortype(S) == Trivial sector, i.e. the case of no symmetries. In that case the data of a tensor t = TensorMap(..., V1 ⊗ ... ⊗ VN₁, W1 ⊗ ... ⊗ WN2) can just be represented as a multidimensional array of size","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(dim(V1), dim(V2), …, dim(VN₁), dim(W1), …, dim(WN₂))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"which can also be reshaped into matrix of size","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(dim(V1)*dim(V2)*…*dim(VN₁), dim(W1)*dim(W2)*…*dim(WN₂))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and is really the matrix representation of the linear map that the tensor represents. In particular, given a second tensor t2 whose domain matches with the codomain of t, function composition amounts to multiplication of their corresponding data matrices. Similarly, tensor factorizations such as the singular value decomposition, which we discuss below, can act directly on this matrix representation.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"note: Note\nOne might wonder if it would not have been more natural to represent the tensor data as (dim(V1), dim(V2), …, dim(VN₁), dim(WN₂), …, dim(W1)) given how employing the duality naturally reverses the tensor product, as encountered with the interface of repartition for fusion trees. However, such a representation, when plainly reshaped to a matrix, would not have the above properties and would thus not constitute the matrix representation of the tensor in a compatible basis.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Now consider the case where sectortype(S) == I for some I which has FusionStyle(I) == UniqueFusion(), i.e. the representations of an Abelian group, e.g. I == Irrep[ℤ₂] or I == Irrep[U₁]. In this case, the tensor data is associated with sectors (a1, a2, …, aN₁) ∈ sectors(V1 ⊗ V2 ⊗ … ⊗ VN₁) and (b1, …, bN₂) ∈ sectors(W1 ⊗ … ⊗ WN₂) such that they fuse to a same common charge, i.e. (c = first(⊗(a1, …, aN₁))) == first(⊗(b1, …, bN₂)). The data associated with this takes the form of a multidimensional array with size (dim(V1, a1), …, dim(VN₁, aN₁), dim(W1, b1), …, dim(WN₂, bN₂)), or equivalently, a matrix of with row size dim(V1, a1)*…*dim(VN₁, aN₁) == dim(codomain, (a1, …, aN₁)) and column size dim(W1, b1)*…*dim(WN₂, aN₂) == dim(domain, (b1, …, bN₂)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"However, there are multiple combinations of (a1, …, aN₁) giving rise to the same c, and so there is data associated with all of these, as well as all possible combinations of (b1, …, bN₂). Stacking all matrices for different (a1,…) and a fixed value of (b1,…) underneath each other, and for fixed value of (a1,…) and different values of (b1,…) next to each other, gives rise to a larger block matrix of all data associated with the central sector c. The size of this matrix is exactly (blockdim(codomain, c), blockdim(domain, c)) and these matrices are exactly the diagonal blocks whose existence is guaranteed by Schur's lemma, and which are labeled by the coupled sector c. Indeed, if we would represent the tensor map t as a matrix without explicitly using the symmetries, we could reorder the rows and columns to group data corresponding to sectors that fuse to the same c, and the resulting block diagonal representation would emerge. This basis transform is thus a permutation, which is a unitary operation, that will cancel or go through trivially for linear algebra operations such as composing tensor maps (matrix multiplication) or tensor factorizations such as a singular value decomposition. For such linear algebra operations, we can thus directly act on these large matrices, which correspond to the diagonal blocks that emerge after a basis transform, provided that the partition of the tensor indices in domain and codomain of the tensor are in line with our needs. For example, composing two tensor maps amounts to multiplying the matrices corresponding to the same c (provided that its subblocks labeled by the different combinations of sectors are ordered in the same way, which we guarantee by associating a canonical order with sectors). Henceforth, we refer to the blocks of a tensor map as those diagonal blocks, the existence of which is provided by Schur's lemma and which are labeled by the coupled sectors c. We directly store these blocks as DenseMatrix and gather them as values in a dictionary, together with the corresponding coupled sector c as key. For a given tensor t, we can access a specific block as block(t, c), whereas blocks(t) yields an iterator over pairs c=>block(t,c).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The subblocks corresponding to a particular combination of sectors then correspond to a particular view for some range of the rows and some range of the colums, i.e. view(block(t, c), m₁:m₂, n₁:n₂) where the ranges m₁:m₂ associated with (a1, …, aN₁) and n₁:n₂ associated with (b₁, …, bN₂) are stored within the fields of the instance t of type TensorMap. This view can then lazily be reshaped to a multidimensional array, for which we rely on the package Strided.jl. Indeed, the data in this view is not contiguous, because the stride between the different columns is larger than the length of the columns. Nonetheless, this does not pose a problem and even as multidimensional array there is still a definite stride associated with each dimension.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When FusionStyle(I) isa MultipleFusion, things become slightly more complicated. Not only do (a1, …, aN₁) give rise to different coupled sectors c, there can be multiply ways in which they fuse to c. These different possibilities are enumerated by the iterator fusiontrees((a1, …, aN₁), c) and fusiontrees((b1, …, bN₂), c), and with each of those, there is tensor data that takes the form of a multidimensional array, or, after reshaping, a matrix of size (dim(codomain, (a1, …, aN₁)), dim(domain, (b1, …, bN₂)))). Again, we can stack all such matrices with the same value of f₁ ∈ fusiontrees((a1, …, aN₁), c) horizontally (as they all have the same number of rows), and with the same value of f₂ ∈ fusiontrees((b1, …, bN₂), c) vertically (as they have the same number of columns). What emerges is a large matrix of size (blockdim(codomain, c), blockdim(domain, c)) containing all the tensor data associated with the coupled sector c, where blockdim(P, c) = sum(dim(P, s)*length(fusiontrees(s, c)) for s in sectors(P)) for some instance P of ProductSpace. The tensor implementation does not distinguish between abelian or non-abelian sectors and still stores these matrices as a DenseMatrix, accessible via block(t, c).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"At first sight, it might now be less clear what the relevance of this block is in relation to the full matrix representation of the tensor map, where the symmetry is not exploited. The essential interpretation is still the same. Schur's lemma now tells that there is a unitary basis transform which makes this matrix representation block diagonal, more specifically, of the form _c B_c 𝟙_c, where B_c denotes block(t,c) and 𝟙_c is an identity matrix of size (dim(c), dim(c)). The reason for this extra identity is that the group representation is recoupled to act as _c 𝟙 u_c(g) for all g mathsfI, with u_c(g) the matrix representation of group element g according to the irrep c. In the abelian case, dim(c) == 1, i.e. all irreducible representations are one-dimensional and Schur's lemma only dictates that all off-diagonal blocks are zero. However, in this case the basis transform to the block diagonal representation is not simply a permutation matrix, but a more general unitary matrix composed of the different fusion trees. Indeed, let us denote the fusion trees f₁ ∈ fusiontrees((a1, …, aN₁), c) as X^a_1 a_N₁_cα where α = (e_1 e_N_1-2 μ₁ μ_N_1-1) is a collective label for the internal sectors e and the vertex degeneracy labels μ of a generic fusion tree, as discussed in the corresponding section. The tensor is then represented as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor storage)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In this diagram, we have indicated how the tensor map can be rewritten in terms of a block diagonal matrix with a unitary matrix on its left and another unitary matrix (if domain and codomain are different) on its right. So the left and right matrices should actually have been drawn as squares. They represent the unitary basis transform. In this picture, red and white regions are zero. The center matrix is most easy to interpret. It is the block diagonal matrix _c B_c 𝟙_c with diagonal blocks labeled by the coupled charge c, in this case it takes two values. Every single small square in between the dotted or dashed lines has size d_c d_c and corresponds to a single element of B_c, tensored with the identity mathrmid_c. Instead of B_c, a more accurate labelling is t^c_(a_1 a_N₁)α (b_1 b_N₂)β where α labels different fusion trees from (a_1 a_N₁) to c. The dashed horizontal lines indicate regions corresponding to different fusion (actually splitting) trees, either because of different sectors (a_1 a_N₁) or different labels α within the same sector. Similarly, the dashed vertical lines define the border between regions of different fusion trees from the domain to c, either because of different sectors (b_1 b_N₂) or a different label β.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To understand this better, we need to understand the basis transform, e.g. on the left (codomain) side. In more detail, it is given by","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor unitary)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Indeed, remembering that V_i = _a_i R_a_i ℂ^n_a_i with R_a_i the representation space on which irrep a_i acts (with dimension mathrmdim(a_i)), we find V_1 V_N_1 = _a_1 a_N₁ (R_a_1 R_a_N_1) ℂ^n_a_1 n_a_N_1. In the diagram above, the wiggly lines correspond to the direct sum over the different sectors (a_1 a_N₁), there depicted taking three possible values (a), (a) and (a). The tensor product (R_a_1 R_a_N_1) ℂ^n_a_1 n_a_N_1 is depicted as (R_a_1 R_a_N_1)^ n_a_1 n_a_N_1, i.e. as a direct sum of the spaces R_(a) = (R_a_1 R_a_N_1) according to the dotted horizontal lines, which repeat n_(a) = n_a_1 n_a_N_1 times. In this particular example, n_(a)=2, n_(a)=3 and n_(a)=5. The thick vertical line represents the separation between the two different coupled sectors, denoted as c and c. Dashed vertical lines represent different ways of reaching the coupled sector, corresponding to different α. In this example, the first sector (a) has one fusion tree to c, labeled by cα, and two fusion trees to c, labeled by cα and cα. The second sector has only a fusion tree to c, labeled by cα. The third sector only has a fusion tree to c, labeld by c α. Finally then, because the fusion trees do not act on the spaces ℂ^n_a_1 n_a_N_1, the dotted lines which represent the different n_(a) = n_a_1 n_a_N_1 dimensions are also drawn vertically. In particular, for a given sector (a) and a specific fusion tree X^(a)_cα R_(a)R_c, the action is X^(a)_cα 𝟙_n_(a), which corresponds to the diagonal green blocks in this drawing where the same matrix X^(a)_cα (the fusion tree) is repeated along the diagonal. Note that the fusion tree is not a vector or single column, but a matrix with number of rows equal to mathrmdim(R_(aldots)) = d_a_1 d_a_2 d_a_N_1 and number of columns equal to d_c. A similar interpretation can be given to the basis transform on the right, by taking its adjoint. In this particular example, it has two different combinations of sectors (b) and (b), where both have a single fusion tree to c as well as to c, and n_(b)=2, n_(b)=3.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that we never explicitly store or act with the basis transforms on the left and the right. For composing tensor maps (i.e. multiplying them), these basis transforms just cancel, whereas for tensor factorizations they just go through trivially. They transform non-trivially when reshuffling the tensor indices, both within or in between the domain and codomain. For this, however, we can completely rely on the manipulations of fusion trees to implicitly compute the effect of the basis transform and construct the new blocks B_c that result with respect to the new basis.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, as before, we only store the diagonal blocks B_c of size (blockdim(codomain(t), c), blockdim(domain(t), c)) as a DenseMatrix, accessible via block(t, c). Within this matrix, there are regions of the form view(block(t, c), m₁:m₂, n₁:n₂) that correspond to the data t^c_(a_1 a_N₁)α (b_1 b_N₂)β associated with a pair of fusion trees X^(a_1 a_N₁)_cα and X^(b_1 b_N₂)_cβ, henceforth again denoted as f₁ and f₂, with f₁.coupled == f₂.coupled == c. The ranges where this subblock is living are managed within the tensor implementation, and these subblocks can be accessed via t[f₁,f₂], and is returned as a StridedArray of size n_a_1 n_a_2 n_a_N_1 n_b_1 n_b_N₂, or in code, (dim(V1, a1), dim(V2, a2), …, dim(VN₁, aN₁), dim(W1, b1), …, dim(WN₂, bN₂)). While the implementation does not distinguish between FusionStyle isa UniqueFusion or FusionStyle isa MultipleFusion, in the former case the fusion tree is completely characterized by the uncoupled sectors, and so the subblocks can also be accessed as t[(a1, …, aN₁, b1, …, bN₂)]. When there is no symmetry at all, i.e. sectortype(t) == Trivial, t[] returns the raw tensor data as a StridedArray of size (dim(V1), …, dim(VN₁), dim(W1), …, dim(WN₂)), whereas block(t, Trivial()) returns the same data as a DenseMatrix of size (dim(V1) * … * dim(VN₁), dim(W1) * … * dim(WN₂)).","category":"page"},{"location":"man/tensors/#ss_tensor_construction","page":"Tensors and the TensorMap type","title":"Constructing tensor maps and accessing tensor data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Having learned how a tensor is represented and stored, we can now discuss how to create tensors and tensor maps. From hereon, we focus purely on the interface rather than the implementation.","category":"page"},{"location":"man/tensors/#Random-and-uninitialized-tensor-maps","page":"Tensors and the TensorMap type","title":"Random and uninitialized tensor maps","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The most convenient set of constructors are those that construct tensors or tensor maps with random or uninitialized data. They take the form","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"f(codomain, domain = one(codomain))\nf(eltype::Type{<:Number}, codomain, domain = one(codomain))\nTensorMap(undef, codomain, domain = one(codomain))\nTensorMap(undef, eltype::Type{<:Number}, codomain, domain = one(codomain))\nTensor(undef, codomain)\nTensor(undef, eltype::Type{<:Number}, codomain)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, f is any of the typical functions from Base that normally create arrays, namely zeros, ones, rand, randn and Random.randexp. Remember that one(codomain) is the empty ProductSpace{S,0}(). The third and fourth calling syntax use the UndefInitializer from Julia Base and generates a TensorMap with unitialized data, which can thus contain NaNs.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In all of these constructors, the last two arguments can be replaced by domain→codomain or codomain←domain, where the arrows are obtained as \\rightarrow+TAB and \\leftarrow+TAB and create a HomSpace as explained in the section on Spaces of morphisms. Some examples are perhaps in order","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t1 = randn(ℂ^2 ⊗ ℂ^3, ℂ^2)\nt2 = zeros(Float32, ℂ^2 ⊗ ℂ^3 ← ℂ^2)\nt3 = TensorMap(undef, ℂ^2 → ℂ^2 ⊗ ℂ^3)\ndomain(t1) == domain(t2) == domain(t3)\ncodomain(t1) == codomain(t2) == codomain(t3)\ndisp(x) = show(IOContext(Core.stdout, :compact=>false), \"text/plain\", trunc.(x; digits = 3));\nt1[] |> disp\nblock(t1, Trivial()) |> disp\nreshape(t1[], dim(codomain(t1)), dim(domain(t1))) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, all constructors can also be replaced by Tensor(..., codomain), in which case the domain is assumed to be the empty ProductSpace{S,0}(), which can easily be obtained as one(codomain). Indeed, the empty product space is the unit object of the monoidal category, equivalent to the field of scalars 𝕜, and thus the multiplicative identity (especially since * also acts as tensor product on vector spaces).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The matrices created by f are the matrices B_c discussed above, i.e. those returned by block(t, c). Only numerical matrices of type DenseMatrix are accepted, which in practice just means Julia's intrinsic Matrix{T} for some T<:Number. In the future, we will add support for CuMatrix from CuArrays.jl to harness GPU computing power, and maybe SharedArray from the Julia's SharedArrays standard library.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Support for static or sparse data is currently unavailable, and if it would be implemented, it would lead to new subtypes of AbstractTensorMap which are distinct from TensorMap. Future implementations of e.g. SparseTensorMap or StaticTensorMap could be useful. Furthermore, there could be specific implementations for tensors whose blocks are Diagonal.","category":"page"},{"location":"man/tensors/#Tensor-maps-from-existing-data","page":"Tensors and the TensorMap type","title":"Tensor maps from existing data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To create a TensorMap with existing data, one can use the aforementioned form but with the function f replaced with the actual data, i.e. TensorMap(data, codomain, domain) or any of its equivalents.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, data can be of two types. It can be a dictionary (any Associative subtype) which has blocksectors c of type sectortype(codomain) as keys, and the corresponding matrix blocks as value, i.e. data[c] is some DenseMatrix of size (blockdim(codomain, c), blockdim(domain, c)). This is the form of how the data is stored within the TensorMap objects.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For those space types for which a TensorMap can be converted to a plain multidimensional array, the data can also be a general DenseArray, either of rank N₁+N₂ and with matching size (dims(codomain)..., dims(domain)...), or just as a DenseMatrix with size (dim(codomain), dim(domain)). This is true in particular if the sector type is Trivial, e.g. for CartesianSpace or ComplexSpace. Then the data array is just reshaped into matrix form and referred to as such in the resulting TensorMap instance. When spacetype is GradedSpace, the TensorMap constructor will try to reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t). This might not be possible, if the data does not respect the symmetry structure. This procedure can be sketched using a simple physical example, namely the SWAP gate on two qubits,","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"beginalign*\nmathrmSWAP mathbbC^2 otimes mathbbC^2 to mathbbC^2 otimes mathbbC^2\nirangle otimes jrangle mapsto jrangle otimes irangle\nendalign*","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This operator can be rewritten in terms of the familiar Heisenberg exchange interaction vecS_i cdot vecS_j as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"mathrmSWAP = 2 vecS_i cdot vecS_j + frac12 𝟙","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where vecS = (S^x S^y S^z) and the spin-1/2 generators of SU₂ S^k are defined defined in terms of the 2 times 2 Pauli matrices sigma^k as S^k = frac12sigma^k. The SWAP gate can be realized as a rank-4 TensorMap in the following way:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"# encode the matrix elements of the swap gate into a rank-4 array, where the first two\n# indices correspond to the codomain and the last two indices correspond to the domain\ndata = zeros(2,2,2,2)\n# the swap gate then maps the last two indices on the first two in reversed order\ndata[1,1,1,1] = data[2,2,2,2] = data[1,2,2,1] = data[2,1,1,2] = 1\nV1 = ℂ^2 # generic qubit hilbert space\nt1 = TensorMap(data, V1 ⊗ V1, V1 ⊗ V1)\nV2 = SU2Space(1/2=>1) # hilbert space of an actual spin-1/2 particle, respecting symmetry\nt2 = TensorMap(data, V2 ⊗ V2, V2 ⊗ V2)\nV3 = U1Space(1/2=>1,-1/2=>1) # restricted space that only uses the `σ_z` rotation symmetry\nt3 = TensorMap(data, V3 ⊗ V3, V3 ⊗ V3)\nfor (c,b) in blocks(t3)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, we recognize that the exchange interaction has eigenvalue -1 in the coupled spin zero sector (SU2Irrep(0)), and eigenvalue +1 in the coupled spin 1 sector (SU2Irrep(1)). Using Irrep[U₁] instead, we observe that both coupled charge U1Irrep(+1) and U1Irrep(-1) have eigenvalue +1. The coupled charge U1Irrep(0) sector is two-dimensional, and has an eigenvalue +1 and an eigenvalue -1.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To construct the proper data in more complicated cases, one has to know where to find each sector in the range 1:dim(V) of every index i with associated space V, as well as the internal structure of the representation space when the corresponding sector c has dim(c)>1, i.e. in the case of FusionStyle(c) isa MultipleFusion. Currently, the only non- abelian sectors are Irrep[SU₂] and Irrep[CU₁], for which the internal structure is the natural one.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are some tools available to facilitate finding the proper range of sector c in space V, namely axes(V, c). This also works on a ProductSpace, with a tuple of sectors. An example","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V = SU2Space(0=>3, 1=>2, 2=>1)\nP = V ⊗ V ⊗ V\naxes(P, (SU2Irrep(1), SU2Irrep(0), SU2Irrep(2)))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the length of the range is the degeneracy dimension of that sector, times the dimension of the internal representation space, i.e. the quantum dimension of that sector.","category":"page"},{"location":"man/tensors/#Assigning-block-data-after-initialization","page":"Tensors and the TensorMap type","title":"Assigning block data after initialization","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In order to avoid having to know the internal structure of each representation space to properly construct the full data array, it is often simpler to assign the block data directly after initializing an all zero TensorMap with the correct spaces. While this may seem more difficult at first sight since it requires knowing the exact entries associated to each valid combination of domain uncoupled sectors, coupled sector and codomain uncoupled sectors, this is often a far more natural procedure in practice.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A first option is to directly set the full matrix block for each coupled sector in the TensorMap. For the example with U₁ symmetry, this can be done as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t4 = zeros(V3 ⊗ V3, V3 ⊗ V3);\nblock(t4, U1Irrep(0)) .= [1 0; 0 1];\nblock(t4, U1Irrep(1)) .= [1;;];\nblock(t4, U1Irrep(-1)) .= [1;;];\nfor (c, b) in blocks(t4)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"While this indeed does not require considering the internal structure of the representation spaces, it still requires knowing the precise row and column indices corresponding to each set of uncoupled sectors in the codmain and domain respectively to correctly assign the nonzero entries in each block.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Perhaps the most natural way of constructing a particular TensorMap is to directly assign the data slices for each splitting - fusion tree pair using the fusiontrees(::TensorMap) method. This returns an iterator over all tuples (f₁, f₂) of splitting - fusion tree pairs corresponding to all ways in which the set of domain uncoupled sectors can fuse to a coupled sector and split back into the set of codomain uncoupled sectors. By directly setting the corresponding data slice t[f₁, f₂] of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...), we can construct all the block data without worrying about the internal ordering of row and column indices in each block. In addition, the corresponding value of each fusion tree slice is often directly informed by the object we are trying to construct in the first place. For example, in order to construct the Heisenberg exchange interaction on two spin-1/2 particles i and j as an SU₂ symmetric TensorMap, we can make use of the observation that","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"vecS_i cdot vecS_j = frac12 left( left( vecS_i cdot vecS_j right)^2 - vecS_i^2 - vecS_j^2 right)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Recalling some basic group theory, we know that the quadratic Casimir of SU₂, vecS^2, has a well-defined eigenvalue j(j+1) on every irrep of spin j. From the above expressions, we can therefore directly read off the eigenvalues of the SWAP gate in terms of this Casimir eigenvalue on the domain uncoupled sectors and the coupled sector. This gives us exactly the prescription we need to assign the data slice corresponding to each splitting - fusion tree pair:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"C(s::SU2Irrep) = s.j * (s.j + 1)\nt5 = zeros(V2 ⊗ V2, V2 ⊗ V2);\nfor (f₁, f₂) in fusiontrees(t5)\n t5[f₁, f₂] .= C(f₂.coupled) - C(f₂.uncoupled[1]) - C(f₂.uncoupled[2]) + 1/2\nend\nfor (c, b) in blocks(t5)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/#Constructing-similar-tensors","page":"Tensors and the TensorMap type","title":"Constructing similar tensors","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A third way to construct a TensorMap instance is to use Base.similar, i.e.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"similar(t [, T::Type{<:Number}, codomain, domain])","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where T is a possibly different eltype for the tensor data, and codomain and domain optionally define a new codomain and domain for the resulting tensor. By default, these values just take the value from the input tensor t. The result will be a new TensorMap instance, with undef data, but whose data is stored in the same subtype of DenseMatrix (e.g. Matrix or CuMatrix or ...) as t. In particular, this uses the methods storagetype(t) and TensorKit.similarstoragetype(t, T).","category":"page"},{"location":"man/tensors/#Special-purpose-constructors","page":"Tensors and the TensorMap type","title":"Special purpose constructors","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are methods zero, one, id, isomorphism, unitary and isometry to create specific new tensors. Tensor maps behave as vectors and can be added (if they have the same domain and codomain); zero(t) is the additive identity, i.e. a TensorMap instance where all entries are zero. For a t::TensorMap with domain(t) == codomain(t), i.e. an endomorphism, one(t) creates the identity tensor, i.e. the identity under composition. As discussed in the section on linear algebra operations, we denote composition of tensor maps with the mutliplication operator *, such that one(t) is the multiplicative identity. Similarly, it can be created as id(V) with V the relevant vector space, e.g. one(t) == id(domain(t)). The identity tensor is currently represented with dense data, and one can use id(A::Type{<:DenseMatrix}, V) to specify the type of DenseMatrix (and its eltype), e.g. A = Matrix{Float64}. Finally, it often occurs that we want to construct a specific isomorphism between two spaces that are isomorphic but not equal, and for which there is no canonical choice. Hereto, one can use the method u = isomorphism([A::Type{<:DenseMatrix}, ] codomain, domain), which will explicitly check that the domain and codomain are isomorphic, and return an error otherwise. Again, an optional first argument can be given to specify the specific type of DenseMatrix that is currently used to store the rather trivial data of this tensor. If InnerProductStyle(u) <: EuclideanProduct, the same result can be obtained with the method u = unitary([A::Type{<:DenseMatrix}, ] codomain, domain). Note that reversing the domain and codomain yields the inverse morphism, which in the case of EuclideanProduct coincides with the adjoint morphism, i.e. isomorphism(A, domain, codomain) == adjoint(u) == inv(u), where inv and adjoint will be further discussed below. Finally, if two spaces V1 and V2 are such that V2 can be embedded in V1, i.e. there exists an inclusion with a left inverse, and furthermore they represent tensor products of some ElementarySpace with EuclideanProduct, the function w = isometry([A::Type{<:DenseMatrix}, ], V1, V2) creates one specific isometric embedding, such that adjoint(w) * w == id(V2) and w * adjoint(w) is some hermitian idempotent (a.k.a. orthogonal projector) acting on V1. An error will be thrown if such a map cannot be constructed for the given domain and codomain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's conclude this section with some examples with GradedSpace.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = ℤ₂Space(0=>3,1=>2)\nV2 = ℤ₂Space(0=>2,1=>1)\n# First a `TensorMap{ℤ₂Space, 1, 1}`\nm = randn(V1, V2)\nconvert(Array, m) |> disp\n# compare with:\nblock(m, Irrep[ℤ₂](0)) |> disp\nblock(m, Irrep[ℤ₂](1)) |> disp\n# Now a `TensorMap{ℤ₂Space, 2, 2}`\nt = randn(V1 ⊗ V1, V2 ⊗ V2')\n(array = convert(Array, t)) |> disp\nd1 = dim(codomain(t))\nd2 = dim(domain(t))\n(matrix = reshape(array, d1, d2)) |> disp\n(u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp\n(v = reshape(convert(Array, unitary(domain(t), fuse(domain(t)))), d2, d2)) |> disp\nu'*u ≈ I ≈ v'*v\n(u'*matrix*v) |> disp\n# compare with:\nblock(t, Z2Irrep(0)) |> disp\nblock(t, Z2Irrep(1)) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, we illustrated some additional concepts. Firstly, note that we convert a TensorMap to an Array. This only works when sectortype(t) supports fusiontensor, and in particular when BraidingStyle(sectortype(t)) == Bosonic(), e.g. the case of trivial tensors (the category mathbfVect) and group representations (the category mathbfRep_mathsfG, which can be interpreted as a subcategory of mathbfVect). Here, we are in this case with mathsfG = ℤ₂. For a TensorMap{S,1,1}, the blocks directly correspond to the diagonal blocks in the block diagonal structure of its representation as an Array, there is no basis transform in between. This is no longer the case for TensorMap{S,N₁,N₂} with different values of N₁ and N₂. Here, we use the operation fuse(V), which creates an ElementarySpace which is isomorphic to a given space V (of type ProductSpace or ElementarySpace). The specific map between those two spaces constructed using the specific method unitary implements precisely the basis change from the product basis to the coupled basis. In this case, for a group G with FusionStyle(Irrep[G]) isa UniqueFusion, it is a permutation matrix. Specifically choosing V equal to the codomain and domain of t, we can construct the explicit basis transforms that bring t into block diagonal form.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's repeat the same exercise for I = Irrep[SU₂], which has FusionStyle(I) isa MultipleFusion.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = SU₂Space(0=>2,1=>1)\nV2 = SU₂Space(0=>1,1=>1)\n# First a `TensorMap{SU₂Space, 1, 1}`\nm = randn(V1, V2)\nconvert(Array, m) |> disp\n# compare with:\nblock(m, Irrep[SU₂](0)) |> disp\nblock(m, Irrep[SU₂](1)) |> disp\n# Now a `TensorMap{SU₂Space, 2, 2}`\nt = randn(V1 ⊗ V1, V2 ⊗ V2')\n(array = convert(Array, t)) |> disp\nd1 = dim(codomain(t))\nd2 = dim(domain(t))\n(matrix = reshape(array, d1, d2)) |> disp\n(u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp\n(v = reshape(convert(Array, unitary(domain(t), fuse(domain(t)))), d2, d2)) |> disp\nu'*u ≈ I ≈ v'*v\n(u'*matrix*v) |> disp\n# compare with:\nblock(t, SU2Irrep(0)) |> disp\nblock(t, SU2Irrep(1)) |> disp\nblock(t, SU2Irrep(2)) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the basis transforms u and v are no longer permutation matrices, but are still unitary. Furthermore, note that they render the tensor block diagonal, but that now every element of the diagonal blocks labeled by c comes itself in a tensor product with an identity matrix of size dim(c), i.e. dim(SU2Irrep(1)) = 3 and dim(SU2Irrep(2)) = 5.","category":"page"},{"location":"man/tensors/#ss_tensor_properties","page":"Tensors and the TensorMap type","title":"Tensor properties","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Given a t::AbstractTensorMap{S,N₁,N₂}, there are various methods to query its properties. The most important are clearly codomain(t) and domain(t). For t::AbstractTensor{S,N}, i.e. t::AbstractTensorMap{S,N,0}, we can use space(t) as synonym for codomain(t). However, for a general AbstractTensorMap this has no meaning. However, we can query space(t, i), the space associated with the ith index. For i ∈ 1:N₁, this corresponds to codomain(t, i) = codomain(t)[i]. For j = i-N₁ ∈ (1:N₂), this corresponds to dual(domain(t, j)) = dual(domain(t)[j]).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The total number of indices, i.e. N₁+N₂, is given by numind(t), with N₁ == numout(t) and N₂ == numin(t), the number of outgoing and incoming indices. There are also the unexported methods TensorKit.codomainind(t) and TensorKit.domainind(t) which return the tuples (1, 2, …, N₁) and (N₁+1, …, N₁+N₂), and are useful for internal purposes. The type parameter S<:ElementarySpace can be obtained as spacetype(t); the corresponding sector can directly obtained as sectortype(t) and is Trivial when S != GradedSpace. The underlying field scalars of S can also directly be obtained as field(t). This is different from eltype(t), which returns the type of Number in the tensor data, i.e. the type parameter T in the (subtype of) DenseMatrix{T} in which the matrix blocks are stored. Note that during construction, a (one-time) warning is printed if !(T ⊂ field(S)). The specific DenseMatrix{T} subtype in which the tensor data is stored is obtained as storagetype(t). Each of the methods numind, numout, numin, TensorKit.codomainind, TensorKit.domainind, spacetype, sectortype, field, eltype and storagetype work in the type domain as well, i.e. they are encoded in typeof(t).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are methods to probe the data, which we already encountered. blocksectors(t) returns an iterator over the different coupled sectors that can be obtained from fusing the uncoupled sectors available in the domain, but they must also be obtained from fusing the uncoupled sectors available in the codomain (i.e. it is the intersection of both blocksectors(codomain(t)) and blocksectors(domain(t))). For a specific sector c ∈ blocksectors(t), block(t, c) returns the corresponding data. Both are obtained together with blocks(t), which returns an iterator over the pairs c=>block(t, c). Furthermore, there is fusiontrees(t) which returns an iterator over splitting-fusion tree pairs (f₁,f₂), for which the corresponding data is given by t[f₁,f₂] (i.e. using Base.getindex).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's again illustrate these methods with an example, continuing with the tensor t from the previous example","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"typeof(t)\ncodomain(t)\ndomain(t)\nspace(t,1)\nspace(t,2)\nspace(t,3)\nspace(t,4)\nnumind(t)\nnumout(t)\nnumin(t)\nspacetype(t)\nsectortype(t)\nfield(t)\neltype(t)\nstoragetype(t)\nblocksectors(t)\nblocks(t)\nblock(t, first(blocksectors(t)))\nfusiontrees(t)\nf1, f2 = first(fusiontrees(t))\nt[f1,f2]","category":"page"},{"location":"man/tensors/#ss_tensor_readwrite","page":"Tensors and the TensorMap type","title":"Reading and writing tensors: Dict conversion","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are no custom or dedicated methods for reading, writing or storing TensorMaps, however, there is the possibility to convert a t::AbstractTensorMap into a Dict, simply as convert(Dict, t). The backward conversion convert(TensorMap, dict) will return a tensor that is equal to t, i.e. t == convert(TensorMap, convert(Dict, t)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This conversion relies on that the string represenation of objects such as VectorSpace, FusionTree or Sector should be such that it represents valid code to recreate the object. Hence, we store information about the domain and codomain of the tensor, and the sector associated with each data block, as a String obtained with repr. This provides the flexibility to still change the internal structure of such objects, without this breaking the ability to load older data files. The resulting dictionary can then be stored using any of the provided Julia packages such as JLD.jl, JLD2.jl, BSON.jl, JSON.jl, ...","category":"page"},{"location":"man/tensors/#ss_tensor_linalg","page":"Tensors and the TensorMap type","title":"Vector space and linear algebra operations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances t represent linear maps, i.e. homomorphisms in a 𝕜-linear category, just like matrices. To a large extent, they follow the interface of Matrix in Julia's LinearAlgebra standard library. Many methods from LinearAlgebra are (re)exported by TensorKit.jl, and can then us be used without using LinearAlgebra explicitly. In all of the following methods, the implementation acts directly on the underlying matrix blocks (typically using the same method) and never needs to perform any basis transforms.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In particular, AbstractTensorMap instances can be composed, provided the domain of the first object coincides with the codomain of the second. Composing tensor maps uses the regular multiplication symbol as in t = t1*t2, which is also used for matrix multiplication. TensorKit.jl also supports (and exports) the mutating method mul!(t, t1, t2). We can then also try to invert a tensor map using inv(t), though this can only exist if the domain and codomain are isomorphic, which can e.g. be checked as fuse(codomain(t)) == fuse(domain(t)). If the inverse is composed with another tensor t2, we can use the syntax t1\\t2 or t2/t1. However, this syntax also accepts instances t1 whose domain and codomain are not isomorphic, and then amounts to pinv(t1), the Moore-Penrose pseudoinverse. This, however, is only really justified as minimizing the least squares problem if InnerProductStyle(t) <: EuclideanProduct.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances behave themselves as vectors (i.e. they are 𝕜-linear) and so they can be multiplied by scalars and, if they live in the same space, i.e. have the same domain and codomain, they can be added to each other. There is also a zero(t), the additive identity, which produces a zero tensor with the same domain and codomain as t. In addition, TensorMap supports basic Julia methods such as fill! and copy!, as well as copy(t) to create a copy with independent data. Aside from basic + and * operations, TensorKit.jl reexports a number of efficient in-place methods from LinearAlgebra, such as axpy! (for y ← α * x + y), axpby! (for y ← α * x + β * y), lmul! and rmul! (for y ← α*y and y ← y*α, which is typically the same) and mul!, which can also be used for out-of-place scalar multiplication y ← α*x.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For t::AbstractTensorMap{S} where InnerProductStyle(S) <: EuclideanProduct, we can compute norm(t), and for two such instances, the inner product dot(t1, t2), provided t1 and t2 have the same domain and codomain. Furthermore, there is normalize(t) and normalize!(t) to return a scaled version of t with unit norm. These operations should also exist for InnerProductStyle(S) <: HasInnerProduct, but require an interface for defining a custom inner product in these spaces. Currently, there is no concrete subtype of HasInnerProduct that is not an EuclideanProduct. In particular, CartesianSpace, ComplexSpace and GradedSpace all have InnerProductStyle(V) <: EuclideanProduct.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"With tensors that have InnerProductStyle(t) <: EuclideanProduct there is associated an adjoint operation, given by adjoint(t) or simply t', such that domain(t') == codomain(t) and codomain(t') == domain(t). Note that for an instance t::TensorMap{S,N₁,N₂}, t' is simply stored in a wrapper called AdjointTensorMap{S,N₂,N₁}, which is another subtype of AbstractTensorMap. This should be mostly unvisible to the user, as all methods should work for this type as well. It can be hard to reason about the index order of t', i.e. index i of t appears in t' at index position j = TensorKit.adjointtensorindex(t, i), where the latter method is typically not necessary and hence unexported. There is also a plural TensorKit.adjointtensorindices to convert multiple indices at once. Note that, because the adjoint interchanges domain and codomain, we have space(t', j) == space(t, i)'.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances can furthermore be tested for exact (t1 == t2) or approximate (t1 ≈ t2) equality, though the latter requires that norm can be computed.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When tensor map instances are endomorphisms, i.e. they have the same domain and codomain, there is a multiplicative identity which can be obtained as one(t) or one!(t), where the latter overwrites the contents of t. The multiplicative identity on a space V can also be obtained using id(A, V) as discussed above, such that for a general homomorphism t′, we have t′ == id(codomain(t′))*t′ == t′*id(domain(t′)). Returning to the case of endomorphisms t, we can compute the trace via tr(t) and exponentiate them using exp(t), or if the contents of t can be destroyed in the process, exp!(t). Furthermore, there are a number of tensor factorizations for both endomorphisms and general homomorphism that we discuss below.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are a number of operations that also belong in this paragraph because of their analogy to common matrix operations. The tensor product of two TensorMap instances t1 and t2 is obtained as t1 ⊗ t2 and results in a new TensorMap with codomain(t1⊗t2) = codomain(t1) ⊗ codomain(t2) and domain(t1⊗t2) = domain(t1) ⊗ domain(t2). If we have two TensorMap{S,N,1} instances t1 and t2 with the same codomain, we can combine them in a way that is analoguous to hcat, i.e. we stack them such that the new tensor catdomain(t1, t2) has also the same codomain, but has a domain which is domain(t1) ⊕ domain(t2). Similarly, if t1 and t2 are of type TensorMap{S,1,N} and have the same domain, the operation catcodomain(t1, t2) results in a new tensor with the same domain and a codomain given by codomain(t1) ⊕ codomain(t2), which is the analogy of vcat. Note that direct sum only makes sense between ElementarySpace objects, i.e. there is no way to give a tensor product meaning to a direct sum of tensor product spaces.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Time for some more examples:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t == t + zero(t) == t*id(domain(t)) == id(codomain(t))*t\nt2 = randn(ComplexF64, codomain(t), domain(t));\ndot(t2, t)\ntr(t2'*t)\ndot(t2, t) ≈ dot(t', t2')\ndot(t2, t2)\nnorm(t2)^2\nt3 = copy!(similar(t, ComplexF64), t);\nt3 == t\nrmul!(t3, 0.8);\nt3 ≈ 0.8*t\naxpby!(0.5, t2, 1.3im, t3);\nt3 ≈ 0.5 * t2 + 0.8 * 1.3im * t\nt4 = randn(fuse(codomain(t)), codomain(t));\nt5 = TensorMap(undef, fuse(codomain(t)), domain(t));\nmul!(t5, t4, t) == t4*t\ninv(t4) * t4 ≈ id(codomain(t))\nt4 * inv(t4) ≈ id(fuse(codomain(t)))\nt4 \\ (t4 * t) ≈ t\nt6 = randn(ComplexF64, V1, codomain(t));\nnumout(t4) == numout(t6) == 1\nt7 = catcodomain(t4, t6);\nforeach(println, (codomain(t4), codomain(t6), codomain(t7)))\nnorm(t7) ≈ sqrt(norm(t4)^2 + norm(t6)^2)\nt8 = t4 ⊗ t6;\nforeach(println, (codomain(t4), codomain(t6), codomain(t8)))\nforeach(println, (domain(t4), domain(t6), domain(t8)))\nnorm(t8) ≈ norm(t4)*norm(t6)","category":"page"},{"location":"man/tensors/#Index-manipulations","page":"Tensors and the TensorMap type","title":"Index manipulations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In many cases, the bipartition of tensor indices (i.e. ElementarySpace instances) between the codomain and domain is not fixed throughout the different operations that need to be performed on that tensor map, i.e. we want to use the duality to move spaces from domain to codomain and vice versa. Furthermore, we want to use the braiding to reshuffle the order of the indices.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For this, we use an interface that is closely related to that for manipulating splitting- fusion tree pairs, namely braid and permute, with the interface","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"braid(t::AbstractTensorMap{S,N₁,N₂}, levels::NTuple{N₁+N₂,Int},\n p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"permute(t::AbstractTensorMap{S,N₁,N₂},\n p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int}; copy = false)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"both of which return an instance of AbstractTensorMap{S,N₁′,N₂′}.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In these methods, p1 and p2 specify which of the original tensor indices ranging from 1 to N₁+N₂ make up the new codomain (with N₁′ spaces) and new domain (with N₂′ spaces). Hence, (p1..., p2...) should be a valid permutation of 1:(N₁+N₂). Note that, throughout TensorKit.jl, permutations are always specified using tuples of Ints, for reasons of type stability. For braid, we also need to specify levels or depths for each of the indices of the original tensor, which determine whether indices will braid over or underneath each other (use the braiding or its inverse). We refer to the section on manipulating fusion trees for more details.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can use the simpler interface of permute, which does not require the argument levels. permute accepts a keyword argument copy. When copy == true, the result will be a tensor with newly allocated data that can independently be modified from that of the input tensor t. When copy takes the default value false, permute can try to return the result in a way that it shares its data with the input tensor t, though this is only possible in specific cases (e.g. when sectortype(S) == Trivial and (p1..., p2...) = (1:(N₁+N₂)...)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Both braid and permute come in a version where the result is stored in an already existing tensor, i.e. braid!(tdst, tsrc, levels, p1, p2) and permute!(tdst, tsrc, p1, p2).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Another operation that belongs und index manipulations is taking the transpose of a tensor, i.e. LinearAlgebra.transpose(t) and LinearAlgebra.transpose!(tdst, tsrc), both of which are reexported by TensorKit.jl. Note that transpose(t) is not simply equal to reshuffling domain and codomain with braid(t, (1:(N₁+N₂)...), reverse(domainind(tsrc)), reverse(codomainind(tsrc)))). Indeed, the graphical representation (where we draw the codomain and domain as a single object), makes clear that this introduces an additional (inverse) twist, which is then compensated in the transpose implementation.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: transpose)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In categorical language, the reason for this extra twist is that we use the left coevaluation η, but the right evaluation tildeϵ, when repartitioning the indices between domain and codomain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are a number of other index related manipulations. We can apply a twist (or inverse twist) to one of the tensor map indices via twist(t, i; inv = false) or twist!(t, i; inv = false). Note that the latter method does not store the result in a new destination tensor, but just modifies the tensor t in place. Twisting several indices simultaneously can be obtained by using the defining property","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"θ_VW = τ_WV (θ_W θ_V) τ_VW = (θ_V θ_W) τ_WV τ_VW","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"but is currently not implemented explicitly.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For all sector types I with BraidingStyle(I) == Bosonic(), all twists are 1 and thus have no effect. Let us start with some examples, in which we illustrate that, albeit permute might act highly non-trivial on the fusion trees and on the corresponding data, after conversion to a regular Array (when possible), it just acts like permutedims","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"domain(t) → codomain(t)\nta = convert(Array, t);\nt′ = permute(t, (1,2,3,4));\ndomain(t′) → codomain(t′)\nconvert(Array, t′) ≈ ta\nt′′ = permute(t, (4,2,3),(1,));\ndomain(t′′) → codomain(t′′)\nconvert(Array, t′′) ≈ permutedims(ta, (4,2,3,1))\nm\ntranspose(m)\nconvert(Array, transpose(t)) ≈ permutedims(ta,(4,3,2,1))\ndot(t2, t) ≈ dot(transpose(t2), transpose(t))\ntranspose(transpose(t)) ≈ t\ntwist(t, 3) ≈ t\n# as twist acts trivially for\nBraidingStyle(sectortype(t))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that transpose acts like one would expect on a TensorMap{S,1,1}. On a TensorMap{S,N₁,N₂}, because transpose replaces the codomain with the dual of the domain, which has its tensor product operation reversed, this in the end amounts in a complete reversal of all tensor indices when representing it as a plain mutli-dimensional Array. Also, note that we have not defined the conjugation of TensorMap instances. One definition that one could think of is conj(t) = adjoint(transpose(t)). However note that codomain(adjoint(tranpose(t))) == domain(transpose(t)) == dual(codomain(t)) and similarly domain(adjoint(tranpose(t))) == dual(domain(t)), where dual of a ProductSpace is composed of the dual of the ElementarySpace instances, in reverse order of tensor product. This might be very confusing, and as such we leave tensor conjugation undefined. However, note that we have a conjugation syntax within the context of tensor contractions.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To show the effect of twist, we now consider a type of sector I for which BraidingStyle{I} != Bosonic(). In particular, we use FibonacciAnyon. We cannot convert the resulting TensorMap to an Array, so we have to rely on indirect tests to verify our results.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = GradedSpace{FibonacciAnyon}(:I=>3,:τ=>2)\nV2 = GradedSpace{FibonacciAnyon}(:I=>2,:τ=>1)\nm = TensorMap(randn, Float32, V1, V2)\ntranspose(m)\ntwist(braid(m, (1,2), (2,), (1,)), 1)\nt1 = randn(V1*V2', V2*V1);\nt2 = randn(ComplexF64, V1*V2', V2*V1);\ndot(t1, t2) ≈ dot(transpose(t1), transpose(t2))\ntranspose(transpose(t1)) ≈ t1","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A final operation that one might expect in this section is to fuse or join indices, and its inverse, to split a given index into two or more indices. For a plain tensor (i.e. with sectortype(t) == Trivial) amount to the equivalent of reshape on the multidimensional data. However, this represents only one possibility, as there is no canonically unique way to embed the tensor product of two spaces V₁ ⊗ V₂ in a new space V = fuse(V₁⊗V₂). Such a mapping can always be accompagnied by a basis transform. However, one particular choice is created by the function isomorphism, or for EuclideanProduct spaces, unitary. Hence, we can join or fuse two indices of a tensor by first constructing u = unitary(fuse(space(t, i) ⊗ space(t, j)), space(t, i) ⊗ space(t, j)) and then contracting this map with indices i and j of t, as explained in the section on contracting tensors. Note, however, that a typical algorithm is not expected to often need to fuse and split indices, as e.g. tensor factorizations can easily be applied without needing to reshape or fuse indices first, as explained in the next section.","category":"page"},{"location":"man/tensors/#ss_tensor_factorization","page":"Tensors and the TensorMap type","title":"Tensor factorizations","text":"","category":"section"},{"location":"man/tensors/#Eigenvalue-decomposition","page":"Tensors and the TensorMap type","title":"Eigenvalue decomposition","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"As tensors are linear maps, they have various kinds of factorizations. Endomorphism, i.e. tensor maps t with codomain(t) == domain(t), have an eigenvalue decomposition. For this, we overload both LinearAlgebra.eigen(t; kwargs...) and LinearAlgebra.eigen!(t; kwargs...), where the latter destroys t in the process. The keyword arguments are the same that are accepted by LinearAlgebra.eigen(!) for matrices. The result is returned as D, V = eigen(t), such that t*V ≈ V*D. For given t::TensorMap{S,N,N}, V is a TensorMap{S,N,1}, whose codomain corresponds to that of t, but whose domain is a single space S (or more correctly a ProductSpace{S,1}), that corresponds to fuse(codomain(t)). The eigenvalues are encoded in D, a TensorMap{S,1,1}, whose domain and codomain correspond to the domain of V. Indeed, we cannot reasonably associate a tensor product structure with the different eigenvalues. Note that D stores the eigenvalues on the diagonal of a (collection of) DenseMatrix instance(s), as there is currently no dedicated DiagonalTensorMap or diagonal storage support.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"We also define LinearAlgebra.ishermitian(t), which can only return true for instances of AbstractEuclideanTensorMap. In all other cases, as the inner product is not defined, there is no notion of hermiticity (i.e. we are not working in a †-category). For instances of EuclideanTensorMap, we also define and export the routines eigh and eigh!, which compute the eigenvalue decomposition under the guarantee (not checked) that the map is hermitian. Hence, eigenvalues will be real and V will be unitary with eltype(V) == eltype(t). We also define and export eig and eig!, which similarly assume that the TensorMap is not hermitian (hence this does not require EuclideanTensorMap), and always returns complex values eigenvalues and eigenvectors. Like for matrices, LinearAlgebra.eigen is type unstable and checks hermiticity at run-time, then falling back to either eig or eigh.","category":"page"},{"location":"man/tensors/#Orthogonal-factorizations","page":"Tensors and the TensorMap type","title":"Orthogonal factorizations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Other factorizations that are provided by TensorKit.jl are orthogonal or unitary in nature, and thus always require a AbstractEuclideanTensorMap. However, they don't require equal domain and codomain. Let us first discuss the singular value decomposition, for which we define and export the methods tsvd and tsvd! (where as always, the latter destroys the input).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"U, Σ, Vʰ, ϵ = tsvd(t; trunc = notrunc(), p::Real = 2,\n alg::OrthogonalFactorizationAlgorithm = SDD())","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This computes a (possibly truncated) singular value decomposition of t::TensorMap{S,N₁,N₂} (with InnerProductStyle(t)<:EuclideanProduct), such that norm(t - U*Σ*Vʰ) ≈ ϵ, where U::TensorMap{S,N₁,1}, S::TensorMap{S,1,1}, Vʰ::TensorMap{S,1,N₂} and ϵ::Real. U is an isometry, i.e. U'*U approximates the identity, whereas U*U' is an idempotent (squares to itself). The same holds for adjoint(Vʰ). The domain of U equals the domain and codomain of Σ and the codomain of Vʰ. In the case of trunc = notrunc() (default value, see below), this space is given by min(fuse(codomain(t)), fuse(domain(t))). The singular values are contained in Σ and are stored on the diagonal of a (collection of) DenseMatrix instance(s), similar to the eigenvalues before.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The keyword argument trunc provides a way to control the truncation, and is connected to the keyword argument p. The default value notrunc() implies no truncation, and thus ϵ = 0. Other valid options are","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"truncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η times the p-norm of all singular values;\ntruncdim(χ::Integer): finds the optimal truncation such that the equivalent total dimension of the internal vector space is no larger than χ;\ntruncspace(W): truncates such that the dimension of the internal vector space is smaller than that of W in any sector, i.e. with W₀ = min(fuse(codomain(t)), fuse(domain(t))) this option will result in domain(U) == domain(Σ) == codomain(Σ) == codomain(Vᵈ) == min(W, W₀);\ntrunbelow(η::Real): truncates such that every singular value is larger then η; this is different from truncerr(η) with p = Inf because it works in absolute rather than relative values.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, the alg keyword can be either SVD() or SDD() (default), which corresponds to two different algorithms in LAPACK to compute singular value decompositions. The default value SDD() uses a divide-and-conquer algorithm and is typically the fastest, but can loose some accuracy. The SVD() method uses a QR-iteration scheme and can be more accurate, but is typically slower. Since Julia 1.3, these two algorithms are also available in the LinearAlgebra standard library, where they are specified as LinearAlgebra.DivideAndConquer() and LinearAlgebra.QRIteration().","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that we defined the new method tsvd (truncated or tensor singular value decomposition), rather than overloading LinearAlgebra.svd. We (will) also support LinearAlgebra.svd(t) as alternative for tsvd(t; trunc = notrunc()), but note that the return values are then given by U, Σ, V = svd(t) with V = adjoint(Vʰ).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"We also define the following pair of orthogonal factorization algorithms, which are useful when one is not interested in truncating a tensor or knowing the singular values, but only in its image or coimage.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Q, R = leftorth(t; alg::OrthogonalFactorizationAlgorithm = QRpos(), kwargs...): this produces an isometry Q::TensorMap{S,N₁,1} (i.e. Q'*Q approximates the identity, Q*Q' is an idempotent, i.e. squares to itself) and a general tensor map R::TensorMap{1,N₂}, such that t ≈ Q*R. Here, the domain of Q and thus codomain of R is a single vector space of type S that is typically given by min(fuse(codomain(t)), fuse(domain(t))).\nThe underlying algorithm used to compute this decomposition can be chosen among QR(), QRpos(), QL(), QLpos(), SVD(), SDD(), Polar(). QR() uses the underlying qr decomposition from LinearAlgebra, while QRpos() (the default) adds a correction to that to make sure that the diagonal elements of R are positive. Both result in upper triangular R, which are square when codomain(t) ≾ domain(t) and wide otherwise. QL() and QLpos() similarly result in a lower triangular matrices in R, but only work in the former case, i.e. codomain(t) ≾ domain(t), which amounts to blockdim(codomain(t), c) >= blockdim(domain(t), c) for all c ∈ blocksectors(t).\nOne can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then set Q=U and R=Σ*Vʰ from the corresponding singular value decomposition, where only these singular values σ >= max(atol, norm(t)*rtol) (and corresponding singular vectors in U) are kept. More finegrained control on the chosen singular values can be obtained with tsvd and its trunc keyword.\nFinally, Polar() sets Q=U*Vʰ and R = (Vʰ)'*Σ*Vʰ, such that R is positive definite; in this case SDD() is used to actually compute the singular value decomposition and no atol or rtol can be provided.\nL, Q = rightorth(t; alg::OrthogonalFactorizationAlgorithm = QRpos()): this produces a general tensor map L::TensorMap{S,N₁,1} and the adjoint of an isometry Q::TensorMap{S,1,N₂}, such that t ≈ L*Q. Here, the domain of L and thus codomain of Q is a single vector space of type S that is typically given by min(fuse(codomain(t)), fuse(domain(t))).\nThe underlying algorithm used to compute this decomposition can be chosen among LQ(), LQpos(), RQ(), RQpos(), SVD(), SDD(), Polar(). LQ() uses the underlying qr decomposition from LinearAlgebra on the transposed data, and leads to lower triangular matrices in L; LQpos() makes sure the diagonal elements are positive. The matrices L are square when codomain(t) ≿ domain(t) and tall otherwise. Similarly, RQ() and RQpos() result in upper triangular matrices in L, but only works if codomain(t) ≿ domain(t), i.e. when blockdim(codomain(t), c) <= blockdim(domain(t), c) for all c ∈ blocksectors(t).\nOne can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then set L=U*Σ and Q=Vʰ from the corresponding singular value decomposition, where only these singular values σ >= max(atol, norm(t)*rtol) (and corresponding singular vectors in Vʰ) are kept. More finegrained control on the chosen singular values can be obtained with tsvd and its trunc keyword.\nFinally, Polar() sets L = U*Σ*U' and Q=U*Vʰ, such that L is positive definite; in this case SDD() is used to actually compute the singular value decomposition and no atol or rtol can be provided.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, we can compute an orthonormal basis for the orthogonal complement of the image and of the co-image (i.e. the kernel) with the following methods:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"N = leftnull(t; alg::OrthogonalFactorizationAlgorithm = QR(), kwargs...): returns an isometric TensorMap{S,N₁,1} (i.e. N'*N approximates the identity) such that N'*t is approximately zero.\nHere, alg can be QR() (QRpos() acts identically in this case), which assumes that t is full rank in all of its blocks and only returns an orthonormal basis for the missing columns.\nIf this is not the case, one can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then construct N from the left singular vectors corresponding to singular values σ < max(atol, norm(t)*rtol).\nN = rightnull(t; alg::OrthogonalFactorizationAlgorithm = QR(), kwargs...): returns a TensorMap{S,1,N₂} with isometric adjoint (i.e. N*N' approximates the identity) such that t*N' is approximately zero.\nHere, alg can be LQ() (LQpos() acts identically in this case), which assumes that t is full rank in all of its blocks and only returns an orthonormal basis for the missing rows.\nIf this is not the case, one can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then construct N from the right singular vectors corresponding to singular values σ < max(atol, norm(t)*rtol).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the methods leftorth, rightorth, leftnull and rightnull also come in a form with exclamation mark, i.e. leftorth!, rightorth!, leftnull! and rightnull!, which destroy the input tensor t.","category":"page"},{"location":"man/tensors/#Factorizations-for-custom-index-bipartions","page":"Tensors and the TensorMap type","title":"Factorizations for custom index bipartions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, note that each of the factorizations take a single argument, the tensor map t, and a number of keyword arguments. They perform the factorization according to the given codomain and domain of the tensor map. In many cases, we want to perform the factorization according to a different bipartition of the indices. When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can immediately specify an alternative bipartition of the indices of t in all of these methods, in the form","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"factorize(t::AbstracTensorMap, pleft::NTuple{N₁′,Int}, pright::NTuple{N₂′,Int}; kwargs...)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where pleft will be the indices in the codomain of the new tensor map, and pright the indices of the domain. Here, factorize is any of the methods LinearAlgebra.eigen, eig, eigh, tsvd, LinearAlgebra.svd, leftorth, rightorth, leftnull and rightnull. This signature does not allow for the exclamation mark, because it amounts to","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"factorize!(permute(t, pleft, pright; copy = true); kwargs...)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where permute was introduced and discussed in the previous section. When the braiding is not symmetric, the user should manually apply braid to bring the tensor map in proper form before performing the factorization.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Some examples to conclude this section","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = SU₂Space(0=>2,1/2=>1)\nV2 = SU₂Space(0=>1,1/2=>1,1=>1)\n\nt = randn(V1 ⊗ V1, V2);\nU, S, W = tsvd(t);\nt ≈ U * S * W\nD, V = eigh(t'*t);\nD ≈ S*S\nU'*U ≈ id(domain(U))\nS\n\nQ, R = leftorth(t; alg = Polar());\nisposdef(R)\nQ ≈ U*W\nR ≈ W'*S*W\n\nU2, S2, W2, ε = tsvd(t; trunc = truncspace(V1));\nW2*W2' ≈ id(codomain(W2))\nS2\nε ≈ norm(block(S, Irrep[SU₂](1)))*sqrt(dim(Irrep[SU₂](1)))\n\nL, Q = rightorth(t, (1,), (2,3));\ncodomain(L), domain(L), domain(Q)\nQ*Q'\nP = Q'*Q;\nP ≈ P*P\nt′ = permute(t, (1,), (2,3));\nt′ ≈ t′ * P","category":"page"},{"location":"man/tensors/#ss_tensor_contraction","page":"Tensors and the TensorMap type","title":"Bosonic tensor contractions and tensor networks","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"One of the most important operation with tensor maps is to compose them, more generally known as contracting them. As mentioned in the section on category theory, a typical composition of maps in a ribbon category can graphically be represented as a planar arrangement of the morphisms (i.e. tensor maps, boxes with lines eminating from top and bottom, corresponding to source and target, i.e. domain and codomain), where the lines connecting the source and targets of the different morphisms should be thought of as ribbons, that can braid over or underneath each other, and that can twist. Technically, we can embed this diagram in ℝ 01 and attach all the unconnected line endings corresponding objects in the source at some position (x0) for xℝ, and all line endings corresponding to objects in the target at some position (x1). The resulting morphism is then invariant under what is known as framed three-dimensional isotopy, i.e. three-dimensional rearrangements of the morphism that respect the rules of boxes connected by ribbons whose open endings are kept fixed. Such a two-dimensional diagram cannot easily be encoded in a single line of code.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"However, things simplify when the braiding is symmetric (such that over- and under- crossings become equivalent, i.e. just crossings), and when twists, i.e. self-crossings in this case, are trivial. This amounts to BraidingStyle(I) == Bosonic() in the language of TensorKit.jl, and is true for any subcategory of mathbfVect, i.e. ordinary tensors, possibly with some symmetry constraint. The case of mathbfSVect and its subcategories, and more general categories, are discussed below.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In the case of triival twists, we can deform the diagram such that we first combine every morphism with a number of coevaluations η so as to represent it as a tensor, i.e. with a trivial domain. We can then rearrange the morphism to be all ligned up horizontally, where the original morphism compositions are now being performed by evaluations ϵ. This process will generate a number of crossings and twists, where the latter can be omitted because they act trivially. Similarly, double crossings can also be omitted. As a consequence, the diagram, or the morphism it represents, is completely specified by the tensors it is composed of, and which indices between the different tensors are connect, via the evaluation ϵ, and which indices make up the source and target of the resulting morphism. If we also compose the resulting morphisms with coevaluations so that it has a trivial domain, we just have one type of unconnected lines, henceforth called open indices. We sketch such a rearrangement in the following picture","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor unitary)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, we can now specify such a tensor diagram, henceforth called a tensor contraction or also tensor network, using a one-dimensional syntax that mimicks abstract index notation and specifies which indices are connected by the evaluation map using Einstein's summation conventation. Indeed, for BraidingStyle(I) == Bosonic(), such a tensor contraction can take the same format as if all tensors were just multi-dimensional arrays. For this, we rely on the interface provided by the package TensorOperations.jl.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The above picture would be encoded as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[a,b,c,d,e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[:] := A[1,2,-4,3]*B[4,5,-3,3]*C[1,-5,4,-2]*D[-1,2,5]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where the latter syntax is known as NCON-style, and labels the unconnected or outgoing indices with negative integers, and the contracted indices with positive integers.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A number of remarks are in order. TensorOperations.jl accepts both integers and any valid variable name as dummy label for indices, and everything in between [ ] is not resolved in the current context but interpreted as a dummy label. Here, we label the indices of a TensorMap, like A::TensorMap{S,N₁,N₂}, in a linear fashion, where the first position corresponds to the first space in codomain(A), and so forth, up to position N₁. Index N₁+1then corresponds to the first space in domain(A). However, because we have applied the coevaluation η, it actually corresponds to the corresponding dual space, in accordance with the interface of space(A, i) that we introduced above, and as indiated by the dotted box around A in the above picture. The same holds for the other tensor maps. Note that our convention also requires that we braid indices that we brought from the domain to the codomain, and so this is only unambiguous for a symmetric braiding, where there is a unique way to permute the indices.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"With the current syntax, we create a new object E because we use the definition operator :=. Furthermore, with the current syntax, it will be a Tensor, i.e. it will have a trivial domain, and correspond to the dotted box in the picture above, rather than the actual morphism E. We can also directly define E with the correct codomain and domain by rather using","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[a b c;d e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[(a,b,c);(d,e)] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where the latter syntax can also be used when the codomain is empty. When using the assignment operator =, the TensorMap E is assumed to exist and the contents will be written to the currently allocated memory. Note that for existing tensors, both on the left hand side and right hand side, trying to specify the indices in the domain and the codomain seperately using the above syntax, has no effect, as the bipartition of indices are already fixed by the existing object. Hence, if E has been created by the previous line of code, all of the following lines are now equivalent","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[(a,b,c);(d,e)] = A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]\n@tensor E[a,b,c,d,e] = A[v w d;x]*B[(y,z,c);(x,)]*C[v e y; b]*D[a,w,z]\n@tensor E[a b; c d e] = A[v; w d x]*B[y,z,c,x]*C[v,e,y,b]*D[a w;z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and none of those will or can change the partition of the indices of E into its codomain and its domain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Two final remarks are in order. Firstly, the order of the tensors appearing on the right hand side is irrelevant, as we can reorder them by using the allowed moves of the Penrose graphical calculus, which yields some crossings and a twist. As the latter is trivial, it can be omitted, and we just use the same rules to evaluate the newly ordered tensor network. For the particular case of matrix matrix multiplication, which also captures more general settings by appropriotely combining spaces into a single line, we indeed find","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor contraction reorder)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or thus, the following to lines of code yield the same result","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor C[i,j] := B[i,k]*A[k,j]\n@tensor C[i,j] := A[k,j]*B[i,k]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Reordering of tensors can be used internally by the @tensor macro to evaluate the contraction in a more efficient manner. In particular, the NCON-style of specifying the contraction gives the user control over the order, and there are other macros, such as @tensoropt, that try to automate this process. There is also an @ncon macro and ncon function, an we recommend reading the manual of TensorOperations.jl to learn more about the possibilities and how they work.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A final remark involves the use of adjoints of tensors. The current framework is such that the user should not be to worried about the actual bipartition into codomain and domain of a given TensorMap instance. Indeed, for factorizations one just specifies the requested bipartition via the factorize(t, pleft, pright) interface, and for tensor contractions the @contract macro figures out the correct manipulations automatically. However, when wanting to use the adjoint of an instance t::TensorMap{S,N₁,N₂}, the resulting adjoint(t) is a AbstractTensorMap{S,N₂,N₁} and one need to know the values of N₁ and N₂ to know exactly where the ith index of t will end up in adjoint(t), and hence to know and understand the index order of t'. Within the @tensor macro, one can instead use conj() on the whole index expression so as to be able to use the original index ordering of t. Indeed, for matrices of thus, TensorMap{S,1,1} instances, this yields exactly the equivalence one expects, namely equivalence between the following to expressions.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor C[i,j] := B'[i,k]*A[k,j]\n@tensor C[i,j] := conj(B[k,i])*A[k,j]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For e.g. an instance A::TensorMap{S,3,2}, the following two syntaxes have the same effect within an @tensor expression: conj(A[a,b,c,d,e]) and A'[d,e,a,b,c].","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Some examples:","category":"page"},{"location":"man/tensors/#Fermionic-tensor-contractions","page":"Tensors and the TensorMap type","title":"Fermionic tensor contractions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"TODO","category":"page"},{"location":"man/tensors/#Anyonic-tensor-contractions","page":"Tensors and the TensorMap type","title":"Anyonic tensor contractions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"TODO","category":"page"},{"location":"man/spaces/#s_spaces","page":"Vector spaces","title":"Vector spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"using TensorKit","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"From the Introduction, it should be clear that an important aspect in the definition of a tensor (map) is specifying the vector spaces and their structure in the domain and codomain of the map. The starting point is an abstract type VectorSpace","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type VectorSpace end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"which is actually a too restricted name. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories, but this can go beyond normal vector spaces (i.e. objects in the category mathbfVect) and even beyond objects of mathbfSVect. However, in order not to make the remaining discussion to abstract or complicated, we will simply refer to subtypes of VectorSpace instead of specific categories, and to spaces (i.e. VectorSpace instances) instead of objects from these categories. In particular, we define two abstract subtypes","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type ElementarySpace <: VectorSpace end\nconst IndexSpace = ElementarySpace\n\nabstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Here, ElementarySpace is a super type for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"On the other hand, subtypes of CompositeSpace{S} where S<:ElementarySpace are composed of a number of elementary spaces of type S. So far, there is a single concrete type ProductSpace{S,N} that represents the tensor product of N vector spaces of a homogeneous type S. Its properties are discussed in the section on Composite spaces, together with possible extensions for the future.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Throughout TensorKit.jl, the function spacetype returns the type of ElementarySpace associated with e.g. a composite space or a tensor. It works both on instances and in the type domain. Its use will be illustrated below.","category":"page"},{"location":"man/spaces/#ss_fields","page":"Vector spaces","title":"Fields","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vector spaces (linear categories) are defined over a field of scalars 𝔽. We define a type hierarchy to specify the scalar field, but so far only support real and complex numbers, via","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type Field end\n\nstruct RealNumbers <: Field end\nstruct ComplexNumbers <: Field end\n\nconst ℝ = RealNumbers()\nconst ℂ = ComplexNumbers()","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that ℝ and ℂ can be typed as \\bbR+TAB and \\bbC+TAB. One reason for defining this new type hierarchy instead of recycling the types from Julia's Number hierarchy is to introduce some syntactic sugar without committing type piracy. In particular, we now have","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"3 ∈ ℝ\n5.0 ∈ ℂ\n5.0+1.0*im ∈ ℝ\nFloat64 ⊆ ℝ\nComplexF64 ⊆ ℂ\nℝ ⊆ ℂ\nℂ ⊆ ℝ","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"and furthermore —probably more usefully— ℝ^n and ℂ^n create specific elementary vector spaces as described in the next section. The underlying field of a vector space or tensor a can be obtained with field(a):","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"field","category":"page"},{"location":"man/spaces/#TensorKit.field-man-spaces","page":"Vector spaces","title":"TensorKit.field","text":"field(V::VectorSpace) -> Field\n\nReturn the field type over which a vector space is defined.\n\n\n\n\n\n","category":"function"},{"location":"man/spaces/#ss_elementaryspaces","page":"Vector spaces","title":"Elementary spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"As mentioned at the beginning of this section, vector spaces that are associated with the individual indices of a tensor should be implemented as subtypes of ElementarySpace. As the domain and codomain of a tensor map will be the tensor product of such objects which all have the same type, it is important that related vector spaces, e.g. the dual space, are objects of the same concrete type (i.e. with the same type parameters in case of a parametric type). In particular, every ElementarySpace should implement the following methods","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"dim(::ElementarySpace) -> ::Int returns the dimension of the space as an Int\ndual(::S) where {S<:ElementarySpace} -> ::S returns the dual space dual(V), using an instance of the same concrete type (i.e. not via type parameters); this should satisfy dual(dual(V))==V\nconj(::S) where {S<:ElementarySpace} -> ::S returns the complex conjugate space conj(V), using an instance of the same concrete type (i.e. not via type parameters); this should satisfy conj(conj(V))==V and we automatically have conj(V::ElementarySpace{ℝ}) = V.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"For convenience, the dual of a space V can also be obtained as V'.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There is concrete type GeneralSpace which is completely characterized by its field 𝔽, its dimension and whether its the dual and/or complex conjugate of 𝔽^d.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct GeneralSpace{𝔽} <: ElementarySpace\n d::Int\n dual::Bool\n conj::Bool\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We furthermore define the trait types","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type InnerProductStyle end\nstruct NoInnerProduct <: InnerProductStyle end\nabstract type HasInnerProduct <: InnerProductStyle end\nstruct EuclideanInnerProduct <: HasInnerProduct end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"to denote for a vector space V whether it has an inner product and thus a canonical mapping from dual(V) to V (for real fields 𝔽 ⊆ ℝ) or from dual(V) to conj(V) (for complex fields). This mapping is provided by the metric, but no further support for working with metrics is currently implemented.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Spaces with the EuclideanInnerProduct style have the natural isomorphisms dual(V) == V (for 𝔽 == ℝ) or dual(V) == conj(V) (for 𝔽 == ℂ). In the language of the previous section on categories, this trait represents dagger or unitary categories, and these vector spaces support an adjoint operation.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In particular, the two concrete types","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct CartesianSpace <: ElementarySpace\n d::Int\nend\nstruct ComplexSpace <: ElementarySpace\n d::Int\n dual::Bool\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"represent the Euclidean spaces ℝ^d or ℂ^d without further inner structure. They can be created using the syntax CartesianSpace(d) == ℝ^d and ComplexSpace(d) == ℂ^d, or ComplexSpace(d, true) == ComplexSpace(d; dual = true) == (ℂ^d)' for the dual space of the latter. Note that the brackets are required because of the precedence rules, since d' == d for d::Integer.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Some examples:","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"dim(ℝ^10)\n(ℝ^10)' == ℝ^10\nisdual((ℂ^5))\nisdual((ℂ^5)')\nisdual((ℝ^5)')\ndual(ℂ^5) == (ℂ^5)' == conj(ℂ^5) == ComplexSpace(5; dual = true)\nfield(ℂ^5)\nfield(ℝ^3)\ntypeof(ℝ^3)\nspacetype(ℝ^3)\nInnerProductStyle(ℝ^3)\nInnerProductStyle(ℂ^5)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"note: Note\nFor ℂ^n the dual space is equal (or naturally isomorphic) to the conjugate space, but not to the space itself. This means that even for ℂ^n, arrows matter in the diagrammatic notation for categories or for tensors, and in particular that a contraction between two tensor indices will check that one is living in the space and the other in the dual space. This is in contrast with several other software packages, especially in the context of tensor networks, where arrows are only introduced when discussing symmetries. We believe that our more purist approach can be useful to detect errors (e.g. unintended contractions). Only with ℝ^n will their be no distinction between a space and its dual. When creating tensors with indices in ℝ^n that have complex data, a one-time warning will be printed, but most operations should continue to work nonetheless.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"One more important instance of ElementarySpace is the GradedSpace, which is used to represent a graded complex vector space with Euclidean inner product, where the grading is provided by the irreducible representations of a group, or more generally, the simple objects of a fusion category. We refer to the subsection on graded spaces on the next page for further information about GradedSpace.","category":"page"},{"location":"man/spaces/#ss_compositespaces","page":"Vector spaces","title":"Composite spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Composite spaces are vector spaces that are built up out of individual elementary vector spaces of the same type. The most prominent (and currently only) example is a tensor product of N elementary spaces of the same type S, which is implemented as","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}\n spaces::NTuple{N, S}\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Given some V1::S, V2::S, V3::S of the same type S<:ElementarySpace, we can easily construct ProductSpace{S,3}((V1,V2,V3)) as ProductSpace(V1,V2,V3) or using V1 ⊗ V2 ⊗ V3, where ⊗ is simply obtained by typing \\otimes+TAB. In fact, for convenience, also the regular multiplication operator * acts as tensor product between vector spaces, and as a consequence so does raising a vector space to a positive integer power, i.e.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"V1 = ℂ^2\nV2 = ℂ^3\nV1 ⊗ V2 ⊗ V1' == V1 * V2 * V1' == ProductSpace(V1,V2,V1') == ProductSpace(V1,V2) ⊗ V1'\nV1^3\ndim(V1 ⊗ V2)\ndims(V1 ⊗ V2)\ndual(V1 ⊗ V2)\nspacetype(V1 ⊗ V2)\nspacetype(ProductSpace{ComplexSpace,3})","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Here, the new function dims maps dim to the individual spaces in a ProductSpace and returns the result as a tuple. Note that the rationale for the last result was explained in the subsection on duality in the introduction to category theory.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Following Julia's Base library, the function one applied to a ProductSpace{S,N} returns the multiplicative identity, which is ProductSpace{S,0}(()). The same result is obtained when acting on an instance V of S::ElementarySpace directly, however note that V ⊗ one(V) will yield a ProductSpace{S,1}(V) and not V itself. The same result can be obtained with ⊗(V). Similar to Julia Base, one also works in the type domain.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In the future, other CompositeSpace types could be added. For example, the wave function of an N-particle quantum system in first quantization would require the introduction of a SymmetricSpace{S,N} or a AntiSymmetricSpace{S,N} for bosons or fermions respectively, which correspond to the symmetric (permutation invariant) or antisymmetric subspace of V^N, where V::S represents the Hilbert space of the single particle system. Other domains, like general relativity, might also benefit from tensors living in a subspace with certain symmetries under specific index permutations.","category":"page"},{"location":"man/spaces/#ss_homspaces","page":"Vector spaces","title":"Space of morphisms","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Given that we define tensor maps as morphisms in a 𝕜-linear monoidal category, i.e. linear maps, we also define a type to denote the corresponding space. Indeed, in a 𝕜-linear category C, the set of morphisms mathrmHom(WV) for VW C is always an actual vector space, irrespective of whether or not C is a subcategory of mathbf(S)Vect.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We introduce the type","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}\n codomain::P1\n domain::P2\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"and can create it as either domain → codomain or codomain ← domain (where the arrows are obtained as \\to+TAB or \\leftarrow+TAB, and as \\rightarrow+TAB respectively). The reason for first listing the codomain and than the domain will become clear in the section on tensor maps.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct. However, HomSpace has a number of properties defined, which we illustrate via examples","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"W = ℂ^2 ⊗ ℂ^3 → ℂ^3 ⊗ dual(ℂ^4)\nfield(W)\ndual(W)\nadjoint(W)\nspacetype(W)\nspacetype(typeof(W))\nW[1]\nW[2]\nW[3]\nW[4]\ndim(W)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that indexing W yields first the spaces in the codomain, followed by the dual of the spaces in the domain. This particular convention is useful in combination with the instances of type TensorMap, which represent morphisms living in such a HomSpace. Also note that dim(W) here seems to be the product of the dimensions of the individual spaces, but that this is no longer true once symmetries are involved. At any time will dim(::HomSpace) represent the number of linearly independent morphisms in this space.","category":"page"},{"location":"man/spaces/#Partial-order-among-vector-spaces","page":"Vector spaces","title":"Partial order among vector spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vector spaces of the same spacetype can be given a partial order, based on whether there exist injective morphisms (a.k.a monomorphisms) or surjective morphisms (a.k.a. epimorphisms) between them. In particular, we define ismonomorphic(V1, V2), with Unicode synonym V1 ≾ V2 (obtained as \\precsim+TAB), to express whether there exist monomorphisms in V1→V2. Similarly, we define isepimorphic(V1, V2), with Unicode synonym V1 ≿ V2 (obtained as \\succsim+TAB), to express whether there exist epimorphisms in V1→V2. Finally, we define isisomorphic(V1, V2), with Unicode alternative V1 ≅ V2 (obtained as \\cong+TAB), to express whether there exist isomorphism in V1→V2. In particular V1 ≅ V2 if and only if V1 ≾ V2 && V1 ≿ V2.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"For completeness, we also export the strict comparison operators ≺ and ≻ (\\prec+TAB and \\succ+TAB), with definitions","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"≺(V1::VectorSpace, V2::VectorSpace) = V1 ≾ V2 && !(V1 ≿ V2)\n≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"However, as we expect these to be less commonly used, no ASCII alternative is provided.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In the context of InnerProductStyle(V) <: EuclideanInnerProduct, V1 ≾ V2 implies that there exists isometries WV1 V2 such that W^ W = mathrmid_V1, while V1 ≅ V2 implies that there exist unitaries UV1V2 such that U^ U = mathrmid_V1 and U U^ = mathrmid_V2.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that spaces that are isomorphic are not necessarily equal. One can be a dual space, and the other a normal space, or one can be an instance of ProductSpace, while the other is an ElementarySpace. There will exist (infinitely) many isomorphisms between the corresponding spaces, but in general none of those will be canonical.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There are also a number of convenience functions to create isomorphic spaces. The function fuse(V1, V2, ...) or fuse(V1 ⊗ V2 ⊗ ...) returns an elementary space that is isomorphic to V1 ⊗ V2 ⊗ .... The function flip(V::ElementarySpace) returns a space that is isomorphic to V but has isdual(flip(V)) == isdual(V'), i.e. if V is a normal space than flip(V) is a dual space. flip(V) is different from dual(V) in the case of GradedSpace. It is useful to flip a tensor index from a ket to a bra (or vice versa), by contracting that index with a unitary map from V1 to flip(V1). We refer to the reference on vector space methods for further information. Some examples:","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"ℝ^3 ≾ ℝ^5\nℂ^3 ≾ (ℂ^5)'\n(ℂ^5) ≅ (ℂ^5)'\nfuse(ℝ^5, ℝ^3)\nfuse(ℂ^3, (ℂ^5)' ⊗ ℂ^2)\nfuse(ℂ^3, (ℂ^5)') ⊗ ℂ^2 ≅ fuse(ℂ^3, (ℂ^5)', ℂ^2) ≅ ℂ^3 ⊗ (ℂ^5)' ⊗ ℂ^2\nflip(ℂ^4)\nflip(ℂ^4) ≅ ℂ^4\nflip(ℂ^4) == ℂ^4","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We also define the direct sum V1 and V2 as V1 ⊕ V2, where ⊕ is obtained by typing \\oplus+TAB. This is possible only if isdual(V1) == isdual(V2). With a little pun on Julia Base, oneunit applied to an elementary space (in the value or type domain) returns the one-dimensional space, which is isomorphic to the scalar field of the space itself. Some examples illustrate this better","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"ℝ^5 ⊕ ℝ^3\nℂ^5 ⊕ ℂ^3\nℂ^5 ⊕ (ℂ^3)'\noneunit(ℝ^3)\nℂ^5 ⊕ oneunit(ComplexSpace)\noneunit((ℂ^3)')\n(ℂ^5) ⊕ oneunit((ℂ^5))\n(ℂ^5)' ⊕ oneunit((ℂ^5)')","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Finally, while spaces have a partial order, there is no unique infimum or supremum of a two or more spaces. However, if V1 and V2 are two ElementarySpace instances with isdual(V1) == isdual(V2), then we can define a unique infimum V::ElementarySpace with the same value of isdual that satisfies V ≾ V1 and V ≾ V2, as well as a unique supremum W::ElementarySpace with the same value of isdual that satisfies W ≿ V1 and W ≿ V2. For CartesianSpace and ComplexSpace, this simply amounts to the space with minimal or maximal dimension, i.e.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"infimum(ℝ^5, ℝ^3)\nsupremum(ℂ^5, ℂ^3)\nsupremum(ℂ^5, (ℂ^3)')","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The names infimum and supremum are especially suited in the case of GradedSpace, as the infimum of two spaces might be different from either of those two spaces, and similar for the supremum.","category":"page"},{"location":"man/sectors/#s_sectorsrepfusion","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"using TensorKit\nimport LinearAlgebra","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Symmetries in a physical system often result in tensors which are invariant under the action of the symmetry group, where this group acts as a tensor product of group actions on every tensor index separately. The group action on a single index, or thus, on the corresponding vector space, can be decomposed into irreducible representations (irreps). Here, we restrict to unitary representations, such that the corresponding vector spaces also have a natural Euclidean inner product. In particular, the Euclidean inner product between two vectors is invariant under the group action and thus transforms according to the trivial representation of the group.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The corresponding vector spaces will be canonically represented as V = _a ℂ^n_a R_a, where a labels the different irreps, n_a is the number of times irrep a appears and R_a is the vector space associated with irrep a. Irreps are also known as spin sectors (in the case of mathsfSU_2) or charge sectors (in the case of mathsfU_1), and we henceforth refer to a as a sector. As discussed in the section on categories, and briefly summarized below, the approach we follow does in fact go beyond the case of irreps of groups, and sectors would more generally correspond to simple objects in a unitary ribbon fusion category. Nonetheless, every step can be appreciated by using the representation theory of mathsfSU_2 or mathsfSU_3 as example. For practical reasons, we assume that there is a canonical order of the sectors, so that the vector space V is completely specified by the values of n_a.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The gain in efficiency (both in memory occupation and computation time) obtained from using (technically: equivariant) tensor maps is that, by Schur's lemma, they are block diagonal in the basis of coupled sectors. To exploit this block diagonal form, it is however essential that we know the basis transform from the individual (uncoupled) sectors appearing in the tensor product form of the domain and codomain, to the totally coupled sectors that label the different blocks. We refer to the latter as block sectors. The transformation from the uncoupled sectors in the domain (or codomain) of the tensor map to the block sector is encoded in a fusion tree (or splitting tree). Essentially, it is a sequential application of pairwise fusion as described by the group's Clebsch–Gordan (CG) coefficients. However, it turns out that we do not need the actual CG coefficients, but only how they transform under transformations such as interchanging the order of the incoming irreps or interchanging incoming and outgoing irreps. This information is known as the topological data of the group, i.e. mainly the F-symbols, which are also known as recoupling coefficients or 6j-symbols (more accurately, the F-symbol is actually Racah's W-coefficients in the case of mathsfSU_2).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Below, we describe how to specify a certain type of sector and what information about them needs to be implemented. Then, we describe how to build a space V composed of a direct sum of different sectors. In the third section, we explain the details of fusion trees, i.e. their construction and manipulation. Finally, we elaborate on the case of general fusion categories and the possibility of having fermionic or anyonic twists. But first, we provide a quick theoretical overview of the required data of the representation theory of a group. We refer to the section on categories, and in particular the subsection on topological data of a unitary fusion category, for further details.","category":"page"},{"location":"man/sectors/#ss_representationtheory","page":"Sectors, graded spaces and fusion trees","title":"Representation theory and unitary fusion categories","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Let the different irreps or sectors be labeled as a, b, c, … First and foremost, we need to specify the fusion rules a b = N^ab_c c with N^ab_c some non-negative integers. There should always exists a unique trivial sector u (called the identity object I or 1 in the language of categories) such that a u = a = u a. Furthermore, there should exist a unique sector bara such that N^abara_u = 1, whereas for all b neq bara, N^ab_u = 0. For unitary irreps of groups, bara corresponds to the complex conjugate of the representation a, or a representation isomorphic to it. For example, for the representations of mathsfSU_2, the trivial sector corresponds to spin zero and all irreps are self-dual (i.e. a = bara), meaning that the conjugate representation is isomorphic to the non-conjugated one (they are however not equal but related by a similarity transform).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The meaning of the fusion rules is that the space of transformations R_a R_b R_c (or vice versa) has dimension N^ab_c. In particular, we assume the existence of a basis consisting of unitary tensor maps X^ab_cμ R_c R_a R_b with μ = 1 N^ab_c such that","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(X^ab_cμ)^ X^ab_cν = δ_μν mathrmid_R_c","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"sum_c sum_μ = 1^N^ab_c X^ab_cμ (X^ab_cμ)^dagger = mathrmid_R_a R_b","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The tensors X^ab_cμ are the splitting tensors, their hermitian conjugate are the fusion tensors. They are only determined up to a unitary basis transform within the space, i.e. acting on the multiplicity label μ = 1 N^ab_c. For mathsfSU_2, where N^ab_c is zero or one and the multiplicity labels are absent, the entries of X^ab_cμ are precisely given by the CG coefficients. The point is that we do not need to know the tensors X^ab_cμ explicitly, but only the topological data of (the representation category of) the group, which describes the following transformation:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"F-move or recoupling: the transformation between (a b) c to a (b c):\n(X^ab_eμ mathrmid_c) X^ec_dν = _fκλ F^abc_d_eμν^fκλ (mathrmid_a X^bc_fκ) X^af_dλ\nBraiding or permuting as defined by τ_ab R_a R_b R_b R_a: τ_R_aR_b X^ab_cμ = _ν R^ab_c^ν_μ X^ba_cν","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The dimensions of the spaces R_a on which representation a acts are denoted as d_a and referred to as quantum dimensions. In particular d_u = 1 and d_a = d_bara. This information is also encoded in the F-symbol as d_a = F^a bara a_a^u_u ^-1. Note that there are no multiplicity labels in that particular F-symbol as N^abara_u = 1.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is a graphical representation associated with the fusion tensors and their manipulations, which we summarize here:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: summary)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As always, we refer to the subsection on topological data of a unitary fusion category for further details.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Finally, for the implementation, it will be useful to distinguish between a number of different possibilities regarding the fusion rules. If, for every a and b, there is a unique c such that a b = c (i.e. N^ab_c = 1 and N^ab_c = 0 for all other c), the category is abelian. Indeed, the representations of a group have this property if and only if the group multiplication law is commutative. In that case, all spaces R_a associated with the representation are one-dimensional and thus trivial. In all other cases, the category is non-abelian. We find it useful to further distinguish between categories which have all N^ab_c equal to zero or one (such that no multiplicity labels are needed), e.g. the representations of mathsfSU_2, and those where some N^ab_c are larger than one, e.g. the representations of mathsfSU_3.","category":"page"},{"location":"man/sectors/#ss_sectors","page":"Sectors, graded spaces and fusion trees","title":"Sectors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We introduce a new abstract type to represent different possible sectors","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type Sector end","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Any concrete subtype of Sector should be such that its instances represent a consistent set of sectors, corresponding to the irreps of some group, or, more generally, the simple objects of a (unitary) fusion category, as reviewed in the subsections on fusion categories and their topological data within the introduction to category theory. Throughout TensorKit.jl, the method sectortype can be used to query the subtype of Sector associated with a particular object, i.e. a vector space, fusion tree, tensor map, or a sector. It works on both instances and in the type domain, and its use will be illustrated further on.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The minimal data to completely specify a type of sector are","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"the fusion rules, i.e. a b = N^ab_c c; this is implemented by a function Nsymbol(a,b,c)\nthe list of fusion outputs from a b; while this information is contained in N^ab_c, it might be costly or impossible to iterate over all possible values of c and test Nsymbol(a,b,c); instead we implement for a ⊗ b to return an iterable object (e.g. tuple, array or a custom Julia type that listens to Base.iterate) and which generates all c for which N^ab_c 0 (just once even if N^ab_c1)\nthe identity object u, such that a u = a = u a; this is implemented by the function one(a) (and also in type domain) from Julia Base\nthe dual or conjugate representation overlinea for which N^abara_u = 1; this is implemented by conj(a) from Julia Base; dual(a) also works as alias, but conj(a) is the method that should be defined\nthe F-symbol or recoupling coefficients F^abc_d^f_e, implemented as the function Fsymbol(a,b,c,d,e,f)\nthe R-symbol R^ab_c, implemented as the function Rsymbol(a,b,c)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For practical reasons, we also require some additional methods to be defined:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"isreal(::Type{<:Sector}) returns whether the topological data of this type of sector is real-valued or not (in which case it is complex-valued). Note that this does not necessarily require that the representation itself, or the Clebsch-Gordan coefficients, are real. There is a fallback implementation that checks whether the F-symbol and R-symbol evaluated with all sectors equal to the identity sector have real eltype.\nhash(a, h) creates a hash of sectors, because sectors and objects created from them are used as keys in lookup tables (i.e. dictionaries)\nisless(a,b) associates a canonical order to sectors (of the same type), in order to unambiguously represent representation spaces V = _a ℂ^n_a R_a.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Further information, such as the quantum dimensions d_a and Frobenius-Schur indicator χ_a (only if a == overlinea) are encoded in the F-symbol. They are obtained as dim(a) and frobeniusschur(a). These functions have default definitions which extract the requested data from Fsymbol(a,conj(a),a,a,one(a),one(a)), but they can be overloaded in case the value can be computed more efficiently.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We also define a parametric type to represent an indexable iterator over the different values of a sector as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SectorValues{I<:Sector} end\nBase.IteratorEltype(::Type{<:SectorValues}) = HasEltype()\nBase.eltype(::Type{SectorValues{I}}) where {I<:Sector} = I\nBase.values(::Type{I}) where {I<:Sector} = SectorValues{I}()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that an instance of the singleton type SectorValues{I} is obtained as values(I). A new sector I<:Sector should define","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.iterate(::SectorValues{I}[, state]) = ...\nBase.IteratorSize(::Type{SectorValues{I}}) = # HasLenght() or IsInfinite()\n# if previous function returns HasLength():\nBase.length(::SectorValues{I}) = ...\nBase.getindex(::SectorValues{I}, i::Int) = ...\nfindindex(::SectorValues{I}, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If the number of values in a sector I is finite (i.e. IteratorSize(values(I)) == HasLength()), the methods getindex and findindex provide a way to map the different sector values from and to the standard range 1, 2, …, length(values(I)). This is used to efficiently represent GradedSpace objects for this type of sector, as discussed in the next section on Graded spaces. Note that findindex acts similar to Base.indexin, but with the order of the arguments reversed (so that is more similar to getindex), and returns an Int rather than an Array{0,Union{Int,Nothing}}.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"It is useful to distinguish between three cases with respect to the fusion rules. For irreps of Abelian groups, we have that for every a and b, there exists a unique c such that a b = c, i.e. there is only a single fusion channel. This follows simply from the fact that all irreps are one-dimensional. All other cases are referred to as non-abelian, i.e. the irreps of a non-abelian group or some more general fusion category. We still distinguish between the case where all entries of N^ab_c 1, i.e. they are zero or one. In that case, F^abc_d^f_e and R^ab_c are scalars. If some N^ab_c 1, it means that the same sector c can appear more than once in the fusion product of a and b, and we need to introduce some multiplicity label μ for the different copies. We implement a \"trait\" (similar to IndexStyle for AbstractArrays in Julia Base), i.e. a type hierarchy","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type FusionStyle end\nstruct UniqueFusion <: FusionStyle # unique fusion output when fusion two sectors\nend\nabstract type MultipleFusion <: FusionStyle end\nstruct SimpleFusion <: MultipleFusion # multiple fusion but multiplicity free\nend\nstruct GenericFusion <: MultipleFusion # multiple fusion with multiplicities\nend\nconst MultiplicityFreeFusion = Union{UniqueFusion, SimpleFusion}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"New sector types I<:Sector should then indicate which fusion style they have by defining FusionStyle(::Type{I}).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In a similar manner, it is useful to distinguish between different styles of braiding. Remember that for group representations, braiding acts as swapping or permuting the vector spaces involved. By definition, applying this operation twice leads us back to the original situation. If that is the case, the braiding is said to be symmetric. For more general fusion categories, associated with the physics of anyonic particles, this is generally not the case and, as a result, permutations of tensor indices are not unambiguously defined. The correct description is in terms of the braid group. This will be discussed in more detail below. Fermions are somewhat in between, as their braiding is symmetric, but they have a non-trivial twist. We thereto define a new type hierarchy","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type BraidingStyle end # generic braiding\nabstract type SymmetricBraiding <: BraidingStyle end\nstruct Bosonic <: SymmetricBraiding end\nstruct Fermionic <: SymmetricBraiding end\nstruct Anyonic <: BraidingStyle end","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"New sector types I<:Sector should then indicate which fusion style they have by defining BraidingStyle(::Type{I}). Note that Bosonic() braiding does not mean that all permutations are trivial and R^ab_c = 1, but that R^ab_c R^ba_c = 1. For example, for the irreps of mathsfSU_2, the R-symbol associated with the fusion of two spin-1/2 particles to spin zero is -1, i.e. the singlet of two spin-1/2 particles is antisymmetric. For a Bosonic() braiding style, all twists are simply +1. The case of fermions and anyons are discussed below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Before discussing in more detail how a new sector type should be implemented, let us study the cases which have already been implemented. Currently, they all correspond to the irreps of groups.","category":"page"},{"location":"man/sectors/#sss_groups","page":"Sectors, graded spaces and fusion trees","title":"Existing group representations","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first sector type is called Trivial, and corresponds to the case where there is actually no symmetry, or thus, the symmetry is the trivial group with only an identity operation and a trivial representation. Its representation theory is particularly simple:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct Trivial <: Sector\nend\nBase.one(a::Sector) = one(typeof(a))\nBase.one(::Type{Trivial}) = Trivial()\nBase.conj(::Trivial) = Trivial()\n⊗(::Trivial, ::Trivial) = (Trivial(),)\nNsymbol(::Trivial, ::Trivial, ::Trivial) = true\nFsymbol(::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial) = 1\nRsymbol(::Trivial, ::Trivial, ::Trivial) = 1\nBase.isreal(::Type{Trivial}) = true\nFusionStyle(::Type{Trivial}) = UniqueFusion()\nBraidingStyle(::Type{Trivial}) = Bosonic()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The Trivial sector type is special cased in the construction of tensors, so that most of these definitions are not actually used.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The most important class of sectors are irreducible representations of groups, for which we have an abstract supertype Irrep{G} that is parameterized on the type of group G. While the specific implementations of Irrep{G} depend on G, one can easily obtain the concrete type without knowing its name as Irrep[G].","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"A number of groups have been defined, namely","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type Group end\nabstract type AbelianGroup <: Group end\n\nabstract type ℤ{N} <: AbelianGroup end\nabstract type U₁ <: AbelianGroup end\nabstract type SU{N} <: Group end\nabstract type CU₁ <: Group end\n\nconst ℤ₂ = ℤ{2}\nconst ℤ₃ = ℤ{3}\nconst ℤ₄ = ℤ{4}\nconst SU₂ = SU{2}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Groups themselves are abstract types without any functionality (at least for now). We also provide a number of convenient Unicode aliases. These group names are probably self- explanatory, except for CU₁ which is explained below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For all group irreps, the braiding style is bosonic","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type AbstractIrrep{G<:Group} <: Sector end # irreps have integer quantum dimensions\nBraidingStyle(::Type{<:AbstractIrrep}) = Bosonic()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"while we gather some more common functionality for irreps of abelian groups (which exhaust all possibilities of fusion categories with abelian fusion)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"const AbelianIrrep{G} = AbstractIrrep{G} where {G<:AbelianGroup}\nFusionStyle(::Type{<:AbelianIrrep}) = UniqueFusion()\nBase.isreal(::Type{<:AbelianIrrep}) = true\n\nNsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = c == first(a ⊗ b)\nFsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:AbelianIrrep} =\n Int(Nsymbol(a, b, e)*Nsymbol(e, c, d)*Nsymbol(b, c, f)*Nsymbol(a, f, d))\nfrobeniusschur(a::AbelianIrrep) = 1\nBsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))\nRsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With these common definition, we implement the representation theory of the two most common Abelian groups, namely ℤ_N","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}\n n::Int8\n function ZNIrrep{N}(n::Integer) where {N}\n @assert N < 64\n new{N}(mod(n, N))\n end\nend\nBase.getindex(::IrrepTable, ::Type{ℤ{N}}) where N = ZNIrrep{N}\nBase.convert(Z::Type{<:ZNIrrep}, n::Real) = Z(n)\n\nBase.one(::Type{ZNIrrep{N}}) where {N} =ZNIrrep{N}(0)\nBase.conj(c::ZNIrrep{N}) where {N} = ZNIrrep{N}(-c.n)\n⊗(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = (ZNIrrep{N}(c1.n+c2.n),)\n\nBase.IteratorSize(::Type{SectorValues{ZNIrrep{N}}}) where N = HasLength()\nBase.length(::SectorValues{ZNIrrep{N}}) where N = N\nBase.iterate(::SectorValues{ZNIrrep{N}}, i = 0) where N =\n return i == N ? nothing : (ZNIrrep{N}(i), i+1)\nBase.getindex(::SectorValues{ZNIrrep{N}}, i::Int) where N =\n 1 <= i <= N ? ZNIrrep{N}(i-1) : throw(BoundsError(values(ZNIrrep{N}), i))\nfindindex(::SectorValues{ZNIrrep{N}}, c::ZNIrrep{N}) where N = c.n + 1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and mathsfU_1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct U1Irrep <: AbstractIrrep{U₁}\n charge::HalfInt\nend\nBase.getindex(::IrrepTable, ::Type{U₁}) = U1Irrep\nBase.convert(::Type{U1Irrep}, c::Real) = U1Irrep(c)\n\nBase.one(::Type{U1Irrep}) = U1Irrep(0)\nBase.conj(c::U1Irrep) = U1Irrep(-c.charge)\n⊗(c1::U1Irrep, c2::U1Irrep) = (U1Irrep(c1.charge+c2.charge),)\n\nBase.IteratorSize(::Type{SectorValues{U1Irrep}}) = IsInfinite()\nBase.iterate(::SectorValues{U1Irrep}, i = 0) =\n return i <= 0 ? (U1Irrep(half(i)), (-i + 1)) : (U1Irrep(half(i)), -i)\n# the following are not used and thus not really necessary\nfunction Base.getindex(::SectorValues{U1Irrep}, i::Int)\n i < 1 && throw(BoundsError(values(U1Irrep), i))\n return U1Irrep(iseven(i) ? half(i>>1) : -half(i>>1))\nend\nfindindex(::SectorValues{U1Irrep}, c::U1Irrep) = (n = twice(c.charge); 2*abs(n)+(n<=0))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The getindex definition just below the type definition provides the mechanism to get the concrete type as Irrep[G] for a given group G. Here, IrrepTable is the singleton type of which the constant Irrep is the only instance. The Base.convert definition allows to convert real numbers to the corresponding type of sector, and thus to omit the type information of the sector whenever this is clear from the context.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In the definition of U1Irrep, HalfInt<:Number is a Julia type defined in HalfIntegers.jl, which is also used for SU2Irrep below, that stores integer or half integer numbers using twice their value. Strictly speaking, the linear representations of U₁ can only have integer charges, and fractional charges lead to a projective representation. It can be useful to allow half integers in order to describe spin 1/2 systems with an axis rotation symmetry. As a user, you should not worry about the details of HalfInt, and additional methods for automatic conversion and pretty printing are provided, as illustrated by the following example","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Irrep[U₁](0.5)\nU1Irrep(0.4)\nU1Irrep(1) ⊗ Irrep[U₁](1//2)\nu = first(U1Irrep(1) ⊗ Irrep[U₁](1//2))\nNsymbol(u, conj(u), one(u))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For ZNIrrep{N}, we use an Int8 for compact storage, assuming that this type will not be used with N>64 (we need 2*(N-1) <= 127 in order for a ⊗ b to work correctly). We also define some aliases for the first (and most commonly used ℤ{N} irreps)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"const Z2Irrep = ZNIrrep{2}\nconst Z3Irrep = ZNIrrep{3}\nconst Z4Irrep = ZNIrrep{4}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"so that we can do","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"z = Z3Irrep(1)\nZNIrrep{3}(1) ⊗ Irrep[ℤ₃](1)\nconj(z)\none(z)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As a further remark, even in the abelian case where a ⊗ b is equivalent to a single new label c, we return it as an iterable container, in this case a one-element tuple (c,).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned above, we also provide the following definitions","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.hash(c::ZNIrrep{N}, h::UInt) where {N} = hash(c.n, h)\nBase.isless(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = isless(c1.n, c2.n)\nBase.hash(c::U1Irrep, h::UInt) = hash(c.charge, h)\nBase.isless(c1::U1Irrep, c2::U1Irrep) where {N} =\n isless(abs(c1.charge), abs(c2.charge)) || zero(HalfInt) < c1.charge == -c2.charge","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Since sectors or objects made out of tuples of sectors (see the section on Fusion Trees below) are often used as keys in look-up tables (i.e. subtypes of AbstractDictionary in Julia), it is important that they can be hashed efficiently. We just hash the sectors above based on their numerical value. Note that hashes will only be used to compare sectors of the same type. The isless function provides a canonical order for sectors of a given type G<:Sector, which is useful to uniquely and unambiguously specify a representation space V = _a ℂ^n_a R_a, as described in the section on Graded spaces below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first example of a non-abelian representation category is that of mathsfSU_2, the implementation of which is summarized by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SU2Irrep <: AbstractIrrep{SU{2}}\n j::HalfInt\nend\n\nBase.one(::Type{SU2Irrep}) = SU2Irrep(zero(HalfInt))\nBase.conj(s::SU2Irrep) = s\n⊗(s1::SU2Irrep, s2::SU2Irrep) = SectorSet{SU2Irrep}(abs(s1.j-s2.j):(s1.j+s2.j))\ndim(s::SU2Irrep) = twice(s.j)+1\nFusionStyle(::Type{SU2Irrep}) = SimpleFusion()\nBase.isreal(::Type{SU2Irrep}) = true\nNsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep) = WignerSymbols.δ(sa.j, sb.j, sc.j)\nFsymbol(s1::SU2Irrep, s2::SU2Irrep, s3::SU2Irrep,\n s4::SU2Irrep, s5::SU2Irrep, s6::SU2Irrep) =\n WignerSymbols.racahW(s1.j, s2.j, s4.j, s3.j, s5.j, s6.j)*sqrt(dim(s5)*dim(s6))\nfunction Rsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep)\n Nsymbol(sa, sb, sc) || return 0.\n iseven(convert(Int, sa.j+sb.j-sc.j)) ? 1.0 : -1.0\nend\n\nBase.IteratorSize(::Type{SectorValues{SU2Irrep}}) = IsInfinite()\nBase.iterate(::SectorValues{SU2Irrep}, i = 0) = (SU2Irrep(half(i)), i+1)\n# unused and not really necessary:\nBase.getindex(::SectorValues{SU2Irrep}, i::Int) =\n 1 <= i ? SU2Irrep(half(i-1)) : throw(BoundsError(values(SU2Irrep), i))\nfindindex(::SectorValues{SU2Irrep}, s::SU2Irrep) = twice(s.j)+1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and some methods for pretty printing and converting from real numbers to irrep labels. As one can notice, the topological data (i.e. Nsymbol and Fsymbol) are provided by the package WignerSymbols.jl. The iterable a ⊗ b is a custom type, that the user does not need to care about. Some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = SU2Irrep(3//2)\nconj(s)\ndim(s)\ncollect(s ⊗ s)\nfor s2 in s ⊗ s\n @show s2\n @show Nsymbol(s, s, s2)\n @show Rsymbol(s, s, s2)\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"A final non-abelian representation theory is that of the semidirect product mathsfU₁ ℤ_2, where in the context of quantum systems, this occurs in the case of systems with particle hole symmetry and the non-trivial element of ℤ_2 acts as charge conjugation C. It has the effect of interchaning mathsfU_1 irreps n and -n, and turns them together in a joint 2-dimensional index, except for the case n=0. Irreps are therefore labeled by integers n 0, however for n=0 the ℤ₂ symmetry can be realized trivially or non-trivially, resulting in an even and odd one- dimensional irrep with mathsfU)_1 charge 0. Given mathsfU_1 mathsfSO_2, this group is also simply known as mathsfO_2, and the two representations with n = 0 are the scalar and pseudo-scalar, respectively. However, because we also allow for half integer representations, we refer to it as Irrep[CU₁] or CU1Irrep in full.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct CU1Irrep <: AbstractIrrep{CU₁}\n j::HalfInt # value of the U1 charge\n s::Int # rep of charge conjugation:\n # if j == 0, s = 0 (trivial) or s = 1 (non-trivial),\n # else s = 2 (two-dimensional representation)\n # Let constructor take the actual half integer value j\n function CU1Irrep(j::Real, s::Int = ifelse(j>zero(j), 2, 0))\n if ((j > zero(j) && s == 2) || (j == zero(j) && (s == 0 || s == 1)))\n new(j, s)\n else\n error(\"Not a valid CU₁ irrep\")\n end\n end\nend\n\nBase.one(::Type{CU1Irrep}) = CU1Irrep(zero(HalfInt), 0)\nBase.conj(c::CU1Irrep) = c\ndim(c::CU1Irrep) = ifelse(c.j == zero(HalfInt), 1, 2)\n\nFusionStyle(::Type{CU1Irrep}) = SimpleFusion()\n...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The rest of the implementation can be read in the source code, but is rather long due to all the different cases for the arguments of Fsymbol.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"So far, no sectors have been implemented with FusionStyle(G) == GenericFusion(), though an example would be the representation theory of mathsfSU_N, i.e. represented by the group SU{N}, for N>2. Such sectors are not yet fully supported; certain operations remain to be implemented. Furthermore, the topological data of the representation theory of such groups is not readily available and needs to be computed.","category":"page"},{"location":"man/sectors/#sss_productsectors","page":"Sectors, graded spaces and fusion trees","title":"Combining different sectors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"It is also possible to define two or more different types of symmetries, e.g. when the total symmetry group is a direct product of individual simple groups. Such sectors are obtained using the binary operator ⊠, which can be entered as \\boxtimes+TAB. First some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"a = Z3Irrep(1) ⊠ Irrep[U₁](1)\ntypeof(a)\nconj(a)\none(a)\ndim(a)\ncollect(a ⊗ a)\nFusionStyle(a)\nb = Irrep[ℤ₃](1) ⊠ Irrep[SU₂](3//2)\ntypeof(b)\nconj(b)\none(b)\ndim(b)\ncollect(b ⊗ b)\nFusionStyle(b)\nc = Irrep[SU₂](1) ⊠ SU2Irrep(3//2)\ntypeof(c)\nconj(c)\none(c)\ndim(c)\ncollect(c ⊗ c)\nFusionStyle(c)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We refer to the source file of ProductSector for implementation details.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The symbol ⊠ refers to the Deligne tensor product within the literature on category theory. Indeed, the category of representation of a product group G₁ × G₂ corresponds the Deligne tensor product of the categories of representations of the two groups separately. But this definition also extends to 𝕜-linear categories which are not the representation category of a group. Note that ⊠ also works in the type domain, i.e. Irrep[ℤ₃] ⊠ Irrep[CU₁] can be used to create ProductSector{Tuple{Irrep[ℤ₃], Irrep[CU₁]}}. Instances of this type can be constructed by giving a number of arguments, where the first argument is used to construct the first sector, and so forth. Furthermore, for representations of groups, we also enabled the notation Irrep[ℤ₃ × CU₁], with × obtained using \\times+TAB. However, this is merely for convience; as Irrep[ℤ₃] ⊠ Irrep[CU₁] is not a subtype of the abstract type AbstractIrrep{ℤ₃ × CU₁}. That behavior cannot be obtained with the Julia's type system. Some more examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"a = Z3Irrep(1) ⊠ Irrep[CU₁](1.5)\na isa Irrep[ℤ₃] ⊠ CU1Irrep\na isa Irrep[ℤ₃ × CU₁]\na isa Irrep{ℤ₃ × CU₁}\na == Irrep[ℤ₃ × CU₁](1, 1.5)","category":"page"},{"location":"man/sectors/#sss_newsectors","page":"Sectors, graded spaces and fusion trees","title":"Defining a new type of sector","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"By now, it should be clear how to implement a new Sector subtype. Ideally, a new I<:Sector type is a struct I ... end (immutable) that has isbitstype(I) == true (see Julia's manual), and implements the following minimal set of methods","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.one(::Type{I}) = I(...)\nBase.conj(a::I) = I(...)\nBase.isreal(::Type{I}) = ... # true or false\nTensorKit.FusionStyle(::Type{I}) = ... # UniqueFusion(), SimpleFusion(), GenericFusion()\nTensorKit.BraidingStyle(::Type{I}) = ... # Bosonic(), Fermionic(), Anyonic()\nTensorKit.Nsymbol(a::I, b::I, c::I) = ...\n # Bool or Integer if FusionStyle(I) == GenericFusion()\nBase.:⊗(a::I, b::I) = ... # some iterable object that generates all possible fusion outputs\nTensorKit.Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I)\nTensorKit.Rsymbol(a::I, b::I, c::I)\nBase.hash(a::I, h::UInt)\nBase.isless(a::I, b::I)\nBase.iterate(::TensorKit.SectorValues{I}[, state]) = ...\nBase.IteratorSize(::Type{TensorKit.SectorValues{I}}) = ... # HasLenght() or IsInfinite()\n# if previous function returns HasLength():\nBase.length(::TensorKit.SectorValues{I}) = ...\nBase.getindex(::TensorKit.SectorValues{I}, i::Int) = ...\nTensorKit.findindex(::TensorKit.SectorValues{I}, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Additionally, suitable definitions can be given for","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TensorKit.dim(a::I) = ...\nTensorKit.frobeniusschur(a::I) = ...\nTensorKit.Bsymbol(a::I, b::I, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Out of these, we have not yet encountered the Frobenius-Schur indicator and the B-symbol. They were both defined in the section on topological data of fusion categories and are fully determined by the F-symbol, just like the quantum dimensions. Hence, there is a default implementation for each of these three functions that just relies on Fsymbol, and alternative definitions need to be given only if a more efficient version is available.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If FusionStyle(I) == GenericFusion(), then the multiple outputs c in the tensor product of a and b will be labeled as i=1, 2, …, Nsymbol(a,b,c). Optionally, a different label can be provided by defining","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TensorKit.vertex_ind2label(i::Int, a::I, b::I, c::I) = ...\n# some label, e.g. a `Char` or `Symbol`","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The following function will then automatically determine the corresponding label type (which should not vary, i.e. vertex_ind2label should be type stable)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"vertex_labeltype(I::Type{<:Sector}) =\n typeof(vertex_ind2label(1, one(I), one(I), one(I)))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The following type, which already appeared in the implementation of SU2Irrep above, can be useful for providing the return type of a ⊗ b","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SectorSet{I<:Sector,F,S}\n f::F\n set::S\nend\n...\nfunction Base.iterate(s::SectorSet{I}, args...) where {I<:Sector}\n next = iterate(s.set, args...)\n next === nothing && return nothing\n val, state = next\n return convert(I, s.f(val)), state\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"That is, SectorSet(f, set) behaves as an iterator that applies x->convert(I, f(x)) on the elements of set; if f is not provided it is just taken as the function identity.","category":"page"},{"location":"man/sectors/#sss_generalsectors","page":"Sectors, graded spaces and fusion trees","title":"Generalizations","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned before, the framework for sectors outlined above depends is in one-to-one correspondence to the topological data for specifying a unitary (spherical and braided, and hence ribbon) fusion category, which was reviewed at the end of the introduction to category theory. For such categories, the objects are not necessarily vector spaces and the fusion and splitting tensors X^ab_cμ do not necessarily exist as actual tensors. However, the morphism spaces c a b still behave as vector spaces, and the X^ab_cμ act as generic basis for that space. As TensorKit.jl does not rely on the X^ab_cμ themselves (even when they do exist) it can also deal with such general fusion categories. Note, though, that when X^ab_cμ does exist, it is available as fusiontensor(a,b,c[,μ]) (even though it is actually the splitting tensor) and can be useful for checking purposes, as illustrated below.","category":"page"},{"location":"man/sectors/#ss_rep","page":"Sectors, graded spaces and fusion trees","title":"Graded spaces","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We have introduced Sector subtypes as a way to label the irreps or sectors in the decomposition V = _a ℂ^n_a R_a. To actually represent such spaces, we now also introduce a corresponding type GradedSpace, which is a subtype of ElementarySpace{ℂ}, i.e.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct GradedSpace{I<:Sector, D} <: ElementarySpace{ℂ}\n dims::D\n dual::Bool\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Here, D is a type parameter to denote the data structure used to store the degeneracy or multiplicity dimensions n_a of the different sectors. For conviency, Vect[I] will return the fully concrete type with D specified.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that, conventionally, a graded vector space is a space that has a natural direct sum decomposition over some set of labels, i.e. V = _a I V_a where the label set I has the structure of a semigroup a b = c I. Here, we generalize this notation by using for I the fusion ring of a fusion category, a b = _c I _μ = 1^N_ab^c c. However, this is mostly to lower the barrier, as really the instances of GradedSpace represent just general objects in a fusion category (or strictly speaking, a pre-fusion category, as we allow for an infinite number of simple objects, e.g. the irreps of a continuous group).","category":"page"},{"location":"man/sectors/#Implementation-details","page":"Sectors, graded spaces and fusion trees","title":"Implementation details","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned, the way in which the degeneracy dimensions n_a are stored depends on the specific sector type I, more specifically on the IteratorSize of values(I). If IteratorSize(values(I)) isa Union{IsInfinite, SizeUnknown}, the different sectors a and their corresponding degeneracy n_a are stored as key value pairs in an Associative array, i.e. a dictionary dims::SectorDict. As the total number of sectors in values(I) can be infinite, only sectors a for which n_a are stored. Here, SectorDict is a constant type alias for a specific dictionary implementation, which currently resorts to SortedVectorDict implemented in TensorKit.jl. Hence, the sectors and their corresponding dimensions are stored as two matching lists (Vector instances), which are ordered based on the property isless(a::I, b::I). This ensures that the space V = _a ℂ^n_a R_a has some unique canonical order in the direct sum decomposition, such that two different but equal instances created independently always match.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If IteratorSize(values(I)) isa Union{HasLength, HasShape}, the degeneracy dimensions n_a are stored for all sectors a ∈ values(I) (also if n_a == 0) in a tuple, more specifically a NTuple{N, Int} with N = length(values(I)). The methods getindex(values(I), i) and findindex(values(I), a) are used to map between a sector a ∈ values(I) and a corresponding index i ∈ 1:N. As N is a compile time constant, these types can be created in a type stable manner.","category":"page"},{"location":"man/sectors/#Constructing-instances","page":"Sectors, graded spaces and fusion trees","title":"Constructing instances","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned, the convenience method Vect[I] will return the concrete type GradedSpace{I,D} with the matching value of D, so that should never be a user's concern. In fact, for consistency, Vect[Trivial] will just return ComplexSpace, which is not even a specific type of GradedSpace. For the specific case of group irreps as sectors, one can use Rep[G] with G the group, as inspired by the categorical name mathbfRep_mathsfG. Some illustrations:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Vect[Trivial]\nVect[U1Irrep]\nVect[Irrep[U₁]]\nRep[U₁]\nRep[ℤ₂ × SU₂]\nVect[Irrep[ℤ₂ × SU₂]]","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that we also have the specific alias U₁Space. In fact, for all the common groups we have a number of alias, both in ASCII and using Unicode:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"# ASCII type aliases\nconst ZNSpace{N} = GradedSpace{ZNIrrep{N}, NTuple{N,Int}}\nconst Z2Space = ZNSpace{2}\nconst Z3Space = ZNSpace{3}\nconst Z4Space = ZNSpace{4}\nconst U1Space = Rep[U₁]\nconst CU1Space = Rep[CU₁]\nconst SU2Space = Rep[SU₂]\n\n# Unicode alternatives\nconst ℤ₂Space = Z2Space\nconst ℤ₃Space = Z3Space\nconst ℤ₄Space = Z4Space\nconst U₁Space = U1Space\nconst CU₁Space = CU1Space\nconst SU₂Space = SU2Space","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"To create specific instances of those types, one can e.g. just use V = GradedSpace(a=>n_a, b=>n_b, c=>n_c) or V = GradedSpace(iterator) where iterator is any iterator (e.g. a dictionary or a generator) that yields Pair{I,Int} instances. With those constructions, I is inferred from the type of sectors. However, it is often more convenient to specify the sector type explicitly (using one of the many alias provided), since then the sectors are automatically converted to the correct type. Thereto, one can use Vect[I], or when I corresponds to the irreducible representations of a group, Rep[G]. Some examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Vect[Irrep[U₁]](0=>3, 1=>2, -1=>1) ==\n GradedSpace(U1Irrep(0)=>3, U1Irrep(1)=>2, U1Irrep(-1)=>1) == \n U1Space(0=>3, 1=>2, -1=>1)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The fact that Rep[G] also works with product groups makes it easy to specify e.g.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Rep[ℤ₂ × SU₂]((0,0) => 3, (1,1/2) => 2, (0,1) => 1) == \n GradedSpace((Z2Irrep(0) ⊠ SU2Irrep(0)) => 3, (Z2Irrep(1) ⊠ SU2Irrep(1/2)) => 2, (Z2Irrep(0) ⊠ SU2Irrep(1)) => 1)","category":"page"},{"location":"man/sectors/#Methods","page":"Sectors, graded spaces and fusion trees","title":"Methods","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There are a number of methods to work with instances V of GradedSpace. The function sectortype returns the type of the sector labels. It also works on other vector spaces, in which case it returns Trivial. The function sectors returns an iterator over the different sectors a with non-zero n_a, for other ElementarySpace types it returns (Trivial,). The degeneracy dimensions n_a can be extracted as dim(V, a), it properly returns 0 if sector a is not present in the decomposition of V. With hassector(V, a) one can check if V contains a sector a with dim(V,a)>0. Finally, dim(V) returns the total dimension of the space V, i.e. _a n_a d_a or thus dim(V) = sum(dim(V,a) * dim(a) for a in sectors(V)). Note that a representation space V has certain sectors a with dimensions n_a, then its dual V' will report to have sectors dual(a), and dim(V', dual(a)) == n_a. There is a subtelty regarding the difference between the dual of a representation space R_a^*, on which the conjugate representation acts, and the representation space of the irrep dual(a)==conj(a) that is isomorphic to the conjugate representation, i.e. R_overlinea R_a^* but they are not equal. We return to this in the section on fusion trees. This is true also in more general fusion categories beyond the representation categories of groups.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Other methods for ElementarySpace, such as dual, fuse and flip also work. In fact, GradedSpace is the reason flip exists, cause in this case it is different then dual. The existence of flip originates from the non-trivial isomorphism between R_overlinea and R_a^*, i.e. the representation space of the dual overlinea of sector a and the dual of the representation space of sector a. In order for flip(V) to be isomorphic to V, it is such that, if V = GradedSpace(a=>n_a,...) then flip(V) = dual(GradedSpace(dual(a)=>n_a,....)).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Furthermore, for two spaces V1 = GradedSpace(a=>n1_a, ...) and V2 = GradedSpace(a=>n2_a, ...), we have infimum(V1,V2) = GradedSpace(a=>min(n1_a,n2_a), ....) and similarly for supremum, i.e. they act on the degeneracy dimensions of every sector separately. Therefore, it can be that the return value of infimum(V1,V2) or supremum(V1,V2) is neither equal to V1 or V2.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For W a ProductSpace{Vect[I], N}, sectors(W) returns an iterator that generates all possible combinations of sectors as represented as NTuple{I,N}. The function dims(W, as) returns the corresponding tuple with degeneracy dimensions, while dim(W, as) returns the product of these dimensions. hassector(W, as) is equivalent to dim(W, as)>0. Finally, there is the function blocksectors(W) which returns a list (of type Vector) with all possible \"block sectors\" or total/coupled sectors that can result from fusing the individual uncoupled sectors in W. Correspondingly, blockdim(W, a) counts the total degeneracy dimension of the coupled sector a in W. The machinery for computing this is the topic of the next section on Fusion trees, but first, it's time for some examples.","category":"page"},{"location":"man/sectors/#Examples","page":"Sectors, graded spaces and fusion trees","title":"Examples","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Let's start with an example involving mathsfU_1:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"V1 = Rep[U₁](0=>3, 1=>2, -1=>1)\nV1 == U1Space(0=>3, 1=>2, -1=>1) == U₁Space(-1=>1, 1=>2,0=>3) # order doesn't matter\n(sectors(V1)...,)\ndim(V1, U1Irrep(1))\ndim(V1', Irrep[U₁](1)) == dim(V1, conj(U1Irrep(1))) == dim(V1, U1Irrep(-1))\nhassector(V1, Irrep[U₁](1))\nhassector(V1, Irrep[U₁](2))\ndual(V1)\nflip(V1)\ndual(V1) ≅ V1\nflip(V1) ≅ V1\nV2 = U1Space(0=>2, 1=>1, -1=>1, 2=>1, -2=>1)\ninfimum(V1, V2)\nsupremum(V1, V2)\n⊕(V1,V2)\nW = ⊗(V1,V2)\ncollect(sectors(W))\ndims(W, (Irrep[U₁](0), Irrep[U₁](0)))\ndim(W, (Irrep[U₁](0), Irrep[U₁](0)))\nhassector(W, (Irrep[U₁](0), Irrep[U₁](0)))\nhassector(W, (Irrep[U₁](2), Irrep[U₁](0)))\nfuse(W)\n(blocksectors(W)...,)\nblockdim(W, Irrep[U₁](0))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and then with mathsfSU_2:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"V1 = Vect[Irrep[SU₂]](0=>3, 1//2=>2, 1=>1)\nV1 == SU2Space(0=>3, 1/2=>2, 1=>1) == SU₂Space(0=>3, 0.5=>2, 1=>1)\n(sectors(V1)...,)\ndim(V1, SU2Irrep(1))\ndim(V1', SU2Irrep(1)) == dim(V1, conj(SU2Irrep(1))) == dim(V1, Irrep[SU₂](1))\ndim(V1)\nhassector(V1, Irrep[SU₂](1))\nhassector(V1, Irrep[SU₂](2))\ndual(V1)\nflip(V1)\nV2 = SU2Space(0=>2, 1//2=>1, 1=>1, 3//2=>1, 2=>1)\ninfimum(V1, V2)\nsupremum(V1, V2)\n⊕(V1,V2)\nW = ⊗(V1,V2)\ncollect(sectors(W))\ndims(W, (Irrep[SU₂](0), Irrep[SU₂](0)))\ndim(W, (Irrep[SU₂](0), Irrep[SU₂](0)))\nhassector(W, (SU2Irrep(0), SU2Irrep(0)))\nhassector(W, (SU2Irrep(2), SU2Irrep(0)))\nfuse(W)\n(blocksectors(W)...,)\nblockdim(W, SU2Irrep(0))","category":"page"},{"location":"man/sectors/#ss_fusiontrees","page":"Sectors, graded spaces and fusion trees","title":"Fusion trees","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The gain in efficiency (both in memory occupation and computation time) obtained from using symmetric (equivariant) tensor maps is that, by Schur's lemma, they are block diagonal in the basis of coupled sectors, i.e. they exhibit block sparsity. To exploit this block diagonal form, it is however essential that we know the basis transform from the individual (uncoupled) sectors appearing in the tensor product form of the domain and codomain, to the totally coupled sectors that label the different blocks. We refer to the latter as block sectors, as we already encountered in the previous section blocksectors and blockdim defined on the type ProductSpace.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"This basis transform consists of a basis of inclusion and projection maps, denoted as X^a_1a_2a_N_cα R_c R_a_1 R_a_2 R_a_N and their adjoints (X^a_1a_2a_N_cα)^, such that","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(X^a_1a_2a_N_cα)^ X^a_1a_2a_N_cα = δ_cc δ_αα mathrmid_c","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"_cα X^a_1a_2a_N_cα (X^a_1a_2a_N_cα)^ = mathrmid_a_1 a_2 a_N = mathrmid_a_1 mathrmid_a_2 mathrmid_a_N","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Fusion trees provide a particular way to construct such a basis. It is useful to know about the existence of fusion trees and how they are represented, as discussed in the first subsection. The next two subsections discuss possible manipulations that can be performed with fusion trees. These are used under the hood when manipulating the indices of tensors, but a typical user would not need to use these manipulations on fusion trees directly. Hence, these last two sections can safely be skipped.","category":"page"},{"location":"man/sectors/#Canonical-representation","page":"Sectors, graded spaces and fusion trees","title":"Canonical representation","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"To couple or fuse the different sectors together into a single block sector, we can sequentially fuse together two sectors into a single coupled sector, which is then fused with the next uncoupled sector, using the splitting tensors X_ab^cμ R_c R_a R_b and their adjoints. This amounts to the canonical choice of our tensor product, and for a given tensor mapping from (((W_1 W_2) W_3) ) W_N_2) to (((V_1 V_2) V_3) ) V_N_1), the corresponding fusion and splitting trees take the form","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: double fusion tree)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"for the specific case N_1=4 and N_2=3. We can separate this tree into the fusing part (b_1b_2)b_3 c and the splitting part c(((a_1a_2)a_3)a_4). Given that the fusion tree can be considered to be the adjoint of a corresponding splitting tree c(b_1b_2)b_3, we now first consider splitting trees in isolation. A splitting tree which goes from one coupled sectors c to N uncoupled sectors a_1, a_2, …, a_N needs N-2 additional internal sector labels e_1, …, e_N-2, and, if FusionStyle(I) isa GenericFusion, N-1 additional multiplicity labels μ_1, …, μ_N-1. We henceforth refer to them as vertex labels, as they are associated with the vertices of the splitting tree. In the case of FusionStyle(I) isa UniqueFusion, the internal sectors e_1, …, e_N-2 are completely fixed, for FusionStyle(I) isa MultipleFusion they can also take different values. In our abstract notation of the splitting basis X^a_1a_2a_N_cα used above, α can be consided a collective label, i.e. α = (e_1 e_N-2 μ₁ μ_N-1). Indeed, we can check the orthogonality condition (X^a_1a_2a_N_cα)^ X^a_1a_2a_N_cα = δ_cc δ_αα mathrmid_c, which now forces all internal lines e_k and vertex labels μ_l to be the same.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is one subtle remark that we have so far ignored. Within the specific subtypes of Sector, we do not explicitly distinguish between R_a^* (simply denoted as a^* and graphically depicted as an upgoing arrow a) and R_bara (simply denoted as bara and depicted with a downgoing arrow), i.e. between the dual space of R_a on which the conjugated irrep acts, or the irrep bara to which the complex conjugate of irrep a is isomorphic. This distinction is however important, when certain uncoupled sectors in the fusion tree actually originate from a dual space. We use the isomorphisms Z_aR_a^* R_bara and its adjoint Z_a^R_baraR_a^*, as introduced in the section on topological data of a fusion category, to build fusion and splitting trees that take the distinction between irreps and their conjugates into account. Hence, in the previous example, if e.g. the first and third space in the codomain and the second space in the domain of the tensor were dual spaces, the actual pair of splitting and fusion tree would look as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: extended double fusion tree)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The presence of these isomorphisms will be important when we start to bend lines, to move uncoupled sectors from the incoming to the outgoing part of the fusion-splitting tree. Note that we can still represent the fusion tree as the adjoint of a corresponding splitting tree, because we also use the adjoint of the Z isomorphisms in the splitting part, and the Z isomorphism in the fusion part. Furthermore, the presence of the Z isomorphisms does not affect the orthonormality.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We represent splitting trees and their adjoints using a specific immutable type called FusionTree (which actually represents a splitting tree, but fusion tree is a more common term), defined as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct FusionTree{I<:Sector,N,M,L,T}\n uncoupled::NTuple{N,I}\n coupled::I\n isdual::NTuple{N,Bool}\n innerlines::NTuple{M,I} # fixed to M = N-2\n vertices::NTuple{L,T} # fixed to L = N-1\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Here, the fields are probably self-explanotary. The isdual field indicates whether an isomorphism is present (if the corresponding value is true) or not. Note that the field uncoupled contains the sectors coming out of the splitting trees, before the possible Z isomorphism, i.e. the splitting tree in the above example would have sectors = (a₁, a₂, a₃, a₄). The FusionTree type has a number of basic properties and capabilities, such as checking for equality with == and support for hash(f::FusionTree, h::UInt), as splitting and fusion trees are used as keys in look-up tables (i.e. AbstractDictionary instances) to look up certain parts of the data of a tensor. The type of L of the vertex labels can be Nothing when they are not needed (i.e. if FusionStyle(I) isa MultiplicityFreeFusion).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"FusionTree instances are not checked for consistency (i.e. valid fusion rules etc) upon creation, hence, they are assumed to be created correctly. The most natural way to create them is by using the fusiontrees(uncoupled::NTuple{N,I}, coupled::I = one(I)) method, which returns an iterator over all possible fusion trees from a set of N uncoupled sectors to a given coupled sector, which by default is assumed to be the trivial sector of that group or fusion category (i.e. the identity object in categorical nomenclature). The return type of fusiontrees is a custom type FusionTreeIterator which conforms to the complete interface of an iterator, and has a custom length function that computes the number of possible fusion trees without iterating over all of them explicitly. This is best illustrated with some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = Irrep[SU₂](1/2)\ncollect(fusiontrees((s,s,s,s)))\ncollect(fusiontrees((s,s,s,s,s), s, (true, false, false, true, false)))\niter = fusiontrees(ntuple(n->s, 16))\nsum(n->1, iter)\nlength(iter)\n@elapsed sum(n->1, iter)\n@elapsed length(iter)\ns2 = s ⊠ s\ncollect(fusiontrees((s2,s2,s2,s2)))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that FusionTree instances are shown (printed) in a way that is valid code to reproduce them, a property which also holds for both instances of Sector and instances of VectorSpace. All of those should be displayed in a way that can be copy pasted as valid code. Furthermore, we use context to determine how to print e.g. a sector. In isolation, s2 is printed as (Irrep[SU₂](1/2) ⊠ Irrep[SU₂](1/2)), however, within the fusion tree, it is simply printed as (1/2, 1/2), because it will be converted back into a ProductSector, namely Irrep[SU₂] ⊠ Irrep[SU₂] by the constructor of FusionTree{Irrep[SU₂] ⊠ Irrep[SU₂]}.","category":"page"},{"location":"man/sectors/#Manipulations-on-a-fusion-tree","page":"Sectors, graded spaces and fusion trees","title":"Manipulations on a fusion tree","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We now discuss elementary manipulations that we want to perform on or between fusion trees (where we actually mean splitting trees), which will form the building block for more general manipulations on a pair of a fusion and splitting tree discussed in the next subsection, and then for casting a general index manipulation of a tensor map as a linear operation in the basis of canonically ordered splitting and fusion trees. In this section, we will ignore the Z isomorphisms, as they are just trivially reshuffled under the different operations that we describe. These manipulations are used as low-level methods by the TensorMap methods discussed on the next page. As such, they are not exported by TensorKit.jl, nor do they overload similarly named methods from Julia Base (see split and merge below).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first operation we discuss is an elementary braid of two neighbouring sectors (indices), i.e. a so-called Artin braid or Artin generator of the braid group. Because these two sectors do not appear on the same fusion vertex, some recoupling is necessary. The following represents two different ways to compute the result of such a braid as a linear combination of new fusion trees in canonical order:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: artin braid)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"While the upper path is the most intuitive, it requires two recouplings or F-moves (one forward and one reverse). On the other hand, the lower path requires only one (reverse) F- move, and two R-moves. The latter are less expensive to compute, and so the lower path is computationally more efficient. However, the end result should be the same, provided the pentagon and hexagon equations are satisfied. We always assume that these are satisfied for any new subtype of Sector, and it is up to the user to verify that they are when implementing new custom Sector types. This result is implemented in the function artin_braid(f::FusionTree, i; inv = false) where i denotes the position of the first sector (i.e. labeled b in the above graph) which is then braided with the sector at position i+1 in the fusion tree f. The keyword argument inv allows to select the inverse braiding operation, which amounts to replacing the R-matrix with its inverse (or thus, adjoint) in the above steps. The result is returned as a dictionary with possible output fusion trees as keys and corresponding coefficients as value. In the case of FusionStyle(I) isa UniqueFusion, their is only one resulting fusion tree, with corresponding coefficient a complex phase (which is one for the bosonic representation theory of an Abelian group), and the result is a special SingletonDict<:AbstractDict, a struct type defined in TensorKit.jl to hold a single key value pair.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With the elementary artin_braid, we can then compute a more general braid. For this, we provide an interface","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"braid(f::FusionTree{I,N}, levels::NTuple{N,Int}, permutation::NTuple{N,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where the braid is specified as a permutation, such that the new sector at position i was originally at position permutation[i], and where every uncoupled sector is also assigned a level or depth. The permutation is decomposed into swaps between neighbouring sectors, and when two sectors are swapped, their respective level will determine whether the left sector is braided over or under its right neighbor. This interface does not allow to specify the most general braid, and in particular will never wind one line around another, but can be used as a more general building block for arbitrary braids than the elementary Artin generators. A graphical example makes this probably more clear, i.e for levels=(1,2,3,4,5) and permutation=(5,3,1,4,2), the corresponding braid is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: braid interface)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"that is, the first sector or space goes to position 3, and crosses over all other lines, because it has the lowest level (i.e. think of level as depth in the third dimension), and so forth. We sketch this operation both as a general braid on the left hand side, and as a particular composition of Artin braids on the right hand side.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"When BraidingStyle(I) == SymmetricBraiding(), there is no distinction between applying the braiding or its inverse (i.e. lines crossing over or under each other in the graphical notation) and the whole operation simplifies down to a permutation. We then also support the interface","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"permute(f::FusionTree{I,N}, permutation::NTuple{N,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Other manipulations which are sometimes needed are","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"insertat(f1::FusionTree{I,N₁}, i::Int, f2::FusionTree{I,N₂}) : inserts a fusion tree f2 at the ith uncoupled sector of fusion tree f1 (this requires that the coupled sector f2 matches with the ith uncoupled sector of f1, and that !f1.isdual[i], i.e. that there is no Z-isomorphism on the ith line of f1), and recouple this into a linear combination of trees in canonical order, with N₁+N₂-1 uncoupled sectors, i.e. diagrammatically for i=3\n(Image: insertat)\nsplit(f::FusionTree{I,N}, M::Int) : splits a fusion tree f into two trees f1 and f2, such that f1 has the first M uncoupled sectors of f, and f2 the remaining N-M. This function is type stable if M is a compile time constant.\nsplit(f, M) is the inverse of insertat in the sence that insertat(f2, 1, f1) should return a dictionary with a single key-value pair f=>1. Diagrammatically, for M=4, the function split returns\n(Image: split)\nmerge(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, c::I, μ=nothing) : merges two fusion trees f1 and f2 by fusing the coupled sectors of f1 and f2 into a sector c (with vertex label μ if FusionStyle(I) == GenericFusion()), and reexpressing the result as a linear combination of fusion trees with N₁+N₂ uncoupled sectors in canonical order. This is a simple application of insertat. Diagrammatically, this operation is represented as:\n(Image: merge)","category":"page"},{"location":"man/sectors/#Manipulations-on-a-splitting-fusion-tree-pair","page":"Sectors, graded spaces and fusion trees","title":"Manipulations on a splitting - fusion tree pair","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In this subsection we discuss manipulations that act on a splitting and fusion tree pair, which we will always as two separate trees f1, f2, where f1 is the splitting tree and f2 represents the fusion tree, and they should have f1.coupled == f2.coupled.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The most important manipulation on such a pair is to move sectors from one to the other. Given the canonical order of these trees, we exclusively use the left duality (see the section on categories), for which the evaluation and coevaluation maps establish isomorphisms between","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"mathrmHom((((b_1 b_2) ) b_N_2) (((a_1 a_2) ) a_N_1))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":" mathrmHom((((b_1 b_2) ) b_N_2-1) ((((a_1 a_2) ) a_N_1) b_N_2^*))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":" mathrmHom(1 (((((((a_1 a_2) ) a_N_1) b_N_2^*) ) b_2^*) b_1^*) )","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where the last morphism space is then labeled by the basis of only splitting trees. We can then use the manipulations from the previous section, and then again use the left duality to bring this back to a pair of splitting and fusion tree with N₂′ incoming and N₁′ incoming sectors (with N₁′ + N₂′ == N₁ + N₂).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We now discuss how to actually bend lines, and thus, move sectors from the incoming part (fusion tree) to the outgoing part (splitting tree). Hereby, we exploit the relations between the (co)evaluation (exact pairing) and the fusion tensors, discussed in topological data of a fusion category. The main ingredient that we need is summarized in","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: line bending)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We will only need the B-symbol and not the A-symbol. Applying the left evaluation on the second sector of a splitting tensor thus yields a linear combination of fusion tensors (when FusionStyle(I) == GenericFusion(), or just a scalar times the corresponding fusion tensor otherwise), with corresponding Z ismorphism. Taking the adjoint of this relation yields the required relation to transform a fusion tensor into a splitting tensor with an added Z^ isomorphism.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"However, we have to be careful if we bend a line on which a Z isomorphism (or its adjoint) is already present. Indeed, it is exactly for this operation that we explicitly need to take the presence of these isomorphisms into account. Indeed, we obtain the relation","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: dual line bending)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Hence, bending an isdual sector from the splitting tree to the fusion tree yields an additional Frobenius-Schur factor, and of course leads to a normal sector (which is no longer isdual and does thus not come with a Z-isomorphism) on the fusion side. We again use the adjoint of this relation to bend an isdual sector from the fusion tree to the splitting tree.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The FusionTree interface to duality and line bending is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"repartition(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, N::Int)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"which takes a splitting tree f1 with N₁ outgoing sectors, a fusion tree f2 with N₂ incoming sectors, and applies line bending such that the resulting splitting and fusion trees have N outgoing sectors, corresponding to the first N sectors out of the list (a_1 a_2 a_N_1 b_N_2^* b_1^*) and N₁+N₂-N incoming sectors, corresponding to the dual of the last N₁+N₂-N sectors from the previous list, in reverse. This return values are correctly inferred if N is a compile time constant.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Graphically, for N₁ = 4, N₂ = 3, N = 2 and some particular choice of isdual in both the fusion and splitting tree:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: repartition)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The result is returned as a dictionary with keys (f1′, f2′) and the corresponding coeff as value. Note that the summation is only over the κ_j labels, such that, in the case of FusionStyle(I) isa MultiplicityFreeFusion, the linear combination simplifies to a single term with a scalar coefficient.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With this basic function, we can now perform arbitrary combinations of braids or permutations with line bendings, to completely reshuffle where sectors appear. The interface provided for this is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"braid(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, levels1::NTuple{N₁,Int}, levels2::NTuple{N₂,Int}, p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where we now have splitting tree f1 with N₁ outgoing sectors, a fusion tree f2 with N₂ incoming sectors, levels1 and levels2 assign a level or depth to the corresponding uncoupled sectors in f1 and f2, and we represent the new configuration as a pair p1 and p2. Together, (p1..., p2...) represents a permutation of length N₁+N₂ = N₁′+N₂′, where p1 indicates which of the original sectors should appear as outgoing sectors in the new splitting tree and p2 indicates which appear as incoming sectors in the new fusion tree. Hereto, we label the uncoupled sectors of f1 from 1 to N₁, followed by the uncoupled sectors of f2 from N₁+1 to N₁+N₂. Note that simply repartitioning the splitting and fusion tree such that e.g. all sectors appear in the new splitting tree (i.e. are outgoing), amounts to chosing p1 = (1,..., N₁, N₁+N₂, N₁+N₂-1, ... , N₁+1) and p2=(), because the duality isomorphism reverses the order of the tensor product.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"This routine is implemented by indeed first making all sectors outgoing using the repartition function discussed above, such that only splitting trees remain, then braiding those using the routine from the previous subsection such that the new outgoing sectors appear first, followed by the new incoming sectors (in reverse order), and then again invoking the repartition routine to bring everything in final form. The result is again returned as a dictionary where the keys are (f1′,f2′) and the values the corresponding coefficients.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As before, there is a simplified interface for the case where BraidingStyle(I) isa SymmetricBraiding and the levels are not needed. This is simply given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"permute(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The braid and permute routines for double fusion trees will be the main access point for corresponding manipulations on tensors. As a consequence, results from this routine are memoized, i.e. they are stored in some package wide 'least-recently used' cache (from LRUCache.jl) that can be accessed as TensorKit.braidcache. By default, this cache stores up to 10^5 different braid or permute resuls, where one result corresponds to one particular combination of (f1, f2, p1, p2, levels1, levels2). This should be sufficient for most algorithms. While there are currently no (official) access methods to change the default settings of this cache (one can always resort to resize!(TensorKit.permutecache) and other methods from LRUCache.jl), this might change in the future. The use of this cache is however controlled by two constants of type RefValue{Bool}, namely usebraidcache_abelian and usebraidcache_nonabelian. The default values are given by TensorKit.usebraidcache_abelian[] = false and TensorKit.usebraidcache_nonabelian[] = true, and respectively reflect that the cache is likely not going to help (or even slow down) fusion trees with FusionStyle(f) isa UniqueFusion, but is probably useful for fusion trees with FusionStyle(f) isa MultipleFusion. One can change these values and test the effect on their application.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The existence of braidcache also implies that potential inefficiencies in the fusion tree manipulations (which we nonetheless try to avoid) will not seriously affect performance of tensor manipulations.","category":"page"},{"location":"man/sectors/#Inspecting-fusion-trees-as-tensors","page":"Sectors, graded spaces and fusion trees","title":"Inspecting fusion trees as tensors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For those cases where the fusion and splitting tensors have an explicit representation as a tensor, i.e. a morphism in the category Vect (this essentially coincides with the case of group representations), this explicit representation can be created, which can be useful for checking purposes. Hereto, it is necessary that the splitting tensor X^ab_cμ, i.e. the Clebsch-Gordan coefficients of the group, are encoded via the routine fusiontensor(a,b,c [,μ = nothing]), where the last argument is only necessary in the case of FusionStyle(I) == GenericFusion(). We can then convert a FusionTree{I,N} into an Array, which will yield a rank N+1 array where the first N dimensions correspond to the uncoupled sectors, and the last dimension to the coupled sector. Note that this is mostly useful for the case of FusionStyle(I) isa MultipleFusion groups, as in the case of abelian groups, all irreps are one-dimensional.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Some examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = Irrep[SU₂](1/2)\niter = fusiontrees((s, s, s, s), SU2Irrep(1))\nf = first(iter)\nconvert(Array, f)\n\nI ≈ convert(Array, FusionTree((SU₂(1/2),), SU₂(1/2), (false,), ()))\nZ = adjoint(convert(Array, FusionTree((SU2Irrep(1/2),), SU2Irrep(1/2), (true,), ())))\ntranspose(Z) ≈ frobeniusschur(SU2Irrep(1/2)) * Z\n\nI ≈ convert(Array, FusionTree((Irrep[SU₂](1),), Irrep[SU₂](1), (false,), ()))\nZ = adjoint(convert(Array, FusionTree((Irrep[SU₂](1),), Irrep[SU₂](1), (true,), ())))\ntranspose(Z) ≈ frobeniusschur(Irrep[SU₂](1)) * Z\n\n#check orthogonality\nfor f1 in iter\n for f2 in iter\n dotproduct = dot(convert(Array, f1), convert(Array, f2))\n println(\"< $f1, $f2> = $dotproduct\")\n end\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that we take the adjoint when computing Z, because convert(Array, f) assumes f to be splitting tree, which is built using Z^. Further note that the normalization (squared) of a fusion tree is given by the dimension of the coupled sector, as we are also tracing over the mathrmid_c when checking the orthogonality by computing dot of the corresponding tensors.","category":"page"},{"location":"man/sectors/#Fermions","page":"Sectors, graded spaces and fusion trees","title":"Fermions","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TODO: Update the documentation for this section.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Fermionic sectors are represented by the type FermionParity, which effectively behaves like a ℤ₂ sector, but with two modifications. Firstly, the exchange of two sectors with odd fermion parity should yield a minus sign, which is taken care of by virtue of the R-symbol. This ensures that permuting tensors behave as expected. Secondly, diagrams with self-crossing lines (aka twists) give rise to a minus sign for odd fermion parity. This is in essence equivalent to having supertraces, which is what ensures that @tensor has a result that is invariant under permutation of its input tensors. This does however lead to unwanted minus signs for certain types of diagrams. To avoid this, the @planar macro does not include a supertrace, but requires a manual resolution of all crossings in the diagram.","category":"page"},{"location":"man/sectors/#Anyons","page":"Sectors, graded spaces and fusion trees","title":"Anyons","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is currently one example of a Sector subtype that has anyonic braiding style, namely that of the Fibonacci fusion category. It has to (isomorphism classes of) simple objects, namely the identity 𝟙 and a non-trivial object known as τ, with fusion rules τ ⊗ τ = 𝟙 ⊕ τ. Let's summarize the topological data","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"𝟙 = FibonacciAnyon(:I)\nτ = FibonacciAnyon(:τ)\ncollect(τ ⊗ τ)\nFusionStyle(τ)\nBraidingStyle(τ)\ndim(𝟙)\ndim(τ)\nF𝟙 = Fsymbol(τ,τ,τ,𝟙,τ,τ)\nFτ = [Fsymbol(τ,τ,τ,τ,𝟙,𝟙) Fsymbol(τ,τ,τ,τ,𝟙,τ); Fsymbol(τ,τ,τ,τ,τ,𝟙) Fsymbol(τ,τ,τ,τ,τ,τ)]\nFτ'*Fτ\npolar(x) = rationalize.((abs(x), angle(x)/(2pi)))\nRsymbol(τ,τ,𝟙) |> polar\nRsymbol(τ,τ,τ) |> polar\ntwist(τ) |> polar","category":"page"},{"location":"index/#Index","page":"Index","title":"Index","text":"","category":"section"},{"location":"index/","page":"Index","title":"Index","text":"","category":"page"},{"location":"man/tutorial/#s_tutorial","page":"Tutorial","title":"Tutorial","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Before discussing at length all aspects of this package, both its usage and implementation, we start with a short tutorial to sketch the main capabilities. Thereto, we start by loading TensorKit.jl","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"using TensorKit","category":"page"},{"location":"man/tutorial/#Cartesian-tensors","page":"Tutorial","title":"Cartesian tensors","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"The most important objects in TensorKit.jl are tensors, which we now create with random (normally distributed) entries in the following manner","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that we entered the tensor size not as plain dimensions, by specifying the vector space associated with these tensor indices, in this case ℝ^n, which can be obtained by typing \\bbR+TAB. The tensor then lives in the tensor product of the different spaces, which we can obtain by typing ⊗ (i.e. \\otimes+TAB), although for simplicity also the usual multiplication sign * does the job. Note also that A is printed as an instance of a parametric type TensorMap, which we will discuss below and contains Tensor.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Let us briefly sidetrack into the nature of ℝ^n:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = ℝ^3\ntypeof(V)\nV == CartesianSpace(3)\nsupertype(CartesianSpace)\nsupertype(ElementarySpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"i.e. ℝ^n can also be created without Unicode using the longer syntax CartesianSpace(n). It is subtype of ElementarySpace, with a standard (Euclidean) inner product over the real numbers. Furthermore,","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"W = ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4\ntypeof(W)\nsupertype(ProductSpace)\nsupertype(CompositeSpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"i.e. the tensor product of a number of CartesianSpaces is some generic parametric ProductSpace type, specifically ProductSpace{CartesianSpace,N} for the tensor product of N instances of CartesianSpace.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Tensors are itself vectors (but not Vectors or even AbstractArrays), so we can compute linear combinations, provided they live in the same space.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(ℝ^3 * ℝ^2 * ℝ^4);\nC = 0.5*A + 2.5*B","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Given that they are behave as vectors, they also have a scalar product and norm, which they inherit from the Euclidean inner product on the individual ℝ^n spaces:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"scalarBA = dot(B,A)\nscalarAA = dot(A,A)\nnormA² = norm(A)^2","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"More generally, our tensor objects implement the full interface layed out in VectorInterface.jl.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"If two tensors live on different spaces, these operations have no meaning and are thus not allowed","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B′ = randn(ℝ^4 * ℝ^2 * ℝ^3);\nspace(B′) == space(A)\nC′ = 0.5*A + 2.5*B′\nscalarBA′ = dot(B′,A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"However, in this particular case, we can reorder the indices of B′ to match space of A, using the routine permute (we deliberately choose not to overload permutedims from Julia Base, for reasons that become clear below):","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"space(permute(B′,(3,2,1))) == space(A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We can contract two tensors using Einstein summation convention, which takes the interface from TensorOperations.jl. TensorKit.jl reexports the @tensor macro","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]\n@tensor d = A[a,b,c]*A[a,b,c]\nd ≈ scalarAA ≈ normA²","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We hope that the index convention is clear. The := is to create a new tensor D, without the : the result would be written in an existing tensor D, which in this case would yield an error as no tensor D exists. If the contraction yields a scalar, regular assignment with = can be used.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we can factorize a tensor, creating a bipartition of a subset of its indices and its complement. With a plain Julia Array, one would apply permutedims and reshape to cast the array into a matrix before applying e.g. the singular value decomposition. With TensorKit.jl, one just specifies which indices go to the left (rows) and right (columns)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"U, S, Vd = tsvd(A, (1,3), (2,));\n@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA ≈ A′\nU","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that the tsvd routine returns the decomposition of the linear map as three factors, U, S and Vd, each of them a TensorMap, such that Vd is already what is commonly called V'. Furthermore, observe that U is printed differently then A, i.e. as a TensorMap((ℝ^3 ⊗ ℝ^4) ← ProductSpace(ℝ^2)). What this means is that tensors (or more appropriately, TensorMap instances) in TensorKit.jl are always considered to be linear maps between two ProductSpace instances, with","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"codomain(U)\ndomain(U)\ncodomain(A)\ndomain(A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"An instance of TensorMap thus represents a linear map from its domain to its codomain, making it an element of the space of homomorphisms between these two spaces. That space is represented using its own type HomSpace in TensorKit.jl, and which admits a direct constructor as well as a unicode alternative using the symbol → (obtained as \\to+TAB or \\rightarrow+TAB) or ← (obtained as \\leftarrow+TAB).","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"P = space(U)\nspace(U) == HomSpace(ℝ^3 ⊗ ℝ^4, ℝ^2) == (ℝ^3 ⊗ ℝ^4 ← ℝ^2) == ℝ^2 → ℝ^3 ⊗ ℝ^4\n(codomain(P), domain(P))","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Furthermore, a Tensor instance such as A is just a specific case of TensorMap with an empty domain, i.e. a ProductSpace{CartesianSpace,0} instance. Analogously, we can represent a vector v and matrix m as","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"v = randn(ℝ^3)\nM₁ = randn(ℝ^4, ℝ^3)\nM₂ = randn(ℝ^4 → ℝ^2) # alternative syntax for randn(ℝ^2, ℝ^4)\nw = M₁ * v # matrix vector product\nM₃ = M₂ * M₁ # matrix matrix product\nspace(M₃)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that for the construction of M₁, in accordance with how one specifies the dimensions of a matrix (e.g. randn(4,3)), the first space is the codomain and the second the domain. This is somewhat opposite to the general notation for a function f:domain→codomain, so that we also support this more mathemical notation, as illustrated in the construction of M₂. However, as this is confusing from the perspective of rows and columns, we also support the syntax codomain ← domain and actually use this as the default way of printing HomSpace instances.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"The 'matrix vector' or 'matrix matrix' product can be computed between any two TensorMap instances for which the domain of the first matches with the codomain of the second, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"v′ = v ⊗ v\nM₁′ = M₁ ⊗ M₁\nw′ = M₁′ * v′\nw′ ≈ w ⊗ w","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Another example involves checking that U from the singular value decomposition is a unitary, or at least a (left) isometric tensor","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"codomain(U)\ndomain(U)\nspace(U)\nU'*U # should be the identity on the corresponding domain = codomain\nU'*U ≈ one(U'*U)\nP = U*U' # should be a projector\nP*P ≈ P","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, the adjoint of a TensorMap results in a new tensor map (actually a simple wrapper of type AdjointTensorMap <: AbstractTensorMap) with domain and codomain interchanged.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Our original tensor A living in ℝ^4 * ℝ^2 * ℝ^3 is isomorphic to e.g. a linear map ℝ^3 → ℝ^4 * ℝ^2. This is where the full power of permute emerges. It allows to specify a permutation where some indices go to the codomain, and others go to the domain, as in","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A2 = permute(A,(1,2),(3,))\ncodomain(A2)\ndomain(A2)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In fact, tsvd(A, (1,3),(2,)) is a shorthand for tsvd(permute(A,(1,3),(2,))), where tsvd(A::TensorMap) will just compute the singular value decomposition according to the given codomain and domain of A.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note, finally, that the @tensor macro treats all indices at the same footing and thus does not distinguish between codomain and domain. The linear numbering is first all indices in the codomain, followed by all indices in the domain. However, when @tensor creates a new tensor (i.e. when using :=), the default syntax always creates a Tensor, i.e. with all indices in the codomain.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\ncodomain(A′)\ndomain(A′)\n@tensor A2′[(a,b);(c,)] := U[a,c,d]*S[d,e]*Vd[e,b];\ncodomain(A2′)\ndomain(A2′)\n@tensor A2′′[a b; c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA2 ≈ A2′ == A2′′","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"As illustrated for A2′ and A2′′, additional syntax is available that enables one to immediately specify the desired codomain and domain indices.","category":"page"},{"location":"man/tutorial/#Complex-tensors","page":"Tutorial","title":"Complex tensors","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"For applications in e.g. quantum physics, we of course want to work with complex tensors. Trying to create a complex-valued tensor with CartesianSpace indices is of course somewhat contrived and prints a (one-time) warning","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ComplexF64, ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"although most of the above operations will work in the expected way (at your own risk). Indeed, we instead want to work with complex vector spaces","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ComplexF64, ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"where ℂ is obtained as \\bbC+TAB and we also have the non-Unicode alternative ℂ^n == ComplexSpace(n). Most functionality works exactly the same","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(ℂ^3 * ℂ^2 * ℂ^4);\nC = im*A + (2.5-0.8im)*B\nscalarBA = dot(B,A)\nscalarAA = dot(A,A)\nnormA² = norm(A)^2\nU,S,Vd = tsvd(A,(1,3),(2,));\n@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA′ ≈ A\npermute(A,(1,3),(2,)) ≈ U*S*Vd","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"However, trying the following","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]\n@tensor d = A[a,b,c]*A[a,b,c]","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"we obtain SpaceMismatch errors. The reason for this is that, with ComplexSpace, an index in a space ℂ^n can only be contracted with an index in the dual space dual(ℂ^n) == (ℂ^n)'. Because of the complex Euclidean inner product, the dual space is equivalent to the complex conjugate space, but not the the space itself.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"dual(ℂ^3) == conj(ℂ^3) == (ℂ^3)'\n(ℂ^3)' == ℂ^3\n@tensor d = conj(A[a,b,c])*A[a,b,c]\nd ≈ normA²","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"This might seem overly strict or puristic, but we believe that it can help to catch errors, e.g. unintended contractions. In particular, contracting two indices both living in ℂ^n would represent an operation that is not invariant under arbitrary unitary basis changes.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"It also makes clear the isomorphism between linear maps ℂ^n → ℂ^m and tensors in ℂ^m ⊗ (ℂ^n)':","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"m = randn(ComplexF64, ℂ^3, ℂ^4)\nm2 = permute(m, (1,2), ())\ncodomain(m2)\nspace(m, 1)\nspace(m, 2)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Hence, spaces become their corresponding dual space if they are 'permuted' from the domain to the codomain or vice versa. Also, spaces in the domain are reported as their dual when probing them with space(A, i). Generalizing matrix vector and matrix matrix multiplication to arbitrary tensor contractions require that the two indices to be contracted have spaces which are each others dual. Knowing this, all the other functionality of tensors with CartesianSpace indices remains the same for tensors with ComplexSpace indices.","category":"page"},{"location":"man/tutorial/#Symmetries","page":"Tutorial","title":"Symmetries","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"So far, the functionality that we have illustrated seems to be just a convenient (or inconvenient?) wrapper around dense multidimensional arrays, e.g. Julia's Base Array. More power becomes visible when involving symmetries. With symmetries, we imply that there is some symmetry action defined on every vector space associated with each of the indices of a TensorMap, and the TensorMap is then required to be equivariant, i.e. it acts as an intertwiner between the tensor product representation on the domain and that on the codomain. By Schur's lemma, this means that the tensor is block diagonal in some basis corresponding to the irreducible representations that can be coupled to by combining the different representations on the different spaces in the domain or codomain. For Abelian symmetries, this does not require a basis change and it just imposes that the tensor has some block sparsity. Let's clarify all of this with some examples.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We start with a simple ℤ₂ symmetry:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V1 = ℤ₂Space(0=>3,1=>2)\ndim(V1)\nV2 = ℤ₂Space(0=>1,1=>1)\ndim(V2)\nA = randn(V1*V1*V2')\nconvert(Array, A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, we create a 5-dimensional space V1, which has a three-dimensional subspace associated with charge 0 (the trivial irrep of ℤ₂) and a two-dimensional subspace with charge 1 (the non-trivial irrep). Similar for V2, where both subspaces are one- dimensional. Representing the tensor as a dense Array, we see that it is zero in those regions where the charges don't add to zero (modulo 2). Of course, the Tensor(Map) type in TensorKit.jl won't store these zero blocks, and only stores the non-zero information, which we can recognize also in the full Array representation.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"From there on, the resulting tensors support all of the same operations as the ones we encountered in the previous examples.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(V1'*V1*V2);\n@tensor C[a,b] := A[a,c,d]*B[c,b,d]\nU,S,V = tsvd(A,(1,3),(2,));\nU'*U # should be the identity on the corresponding domain = codomain\nU'*U ≈ one(U'*U)\nP = U*U' # should be a projector\nP*P ≈ P","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We also support other abelian symmetries, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = U₁Space(0=>2,1=>1,-1=>1)\ndim(V)\nA = randn(V*V, V)\ndim(A)\nconvert(Array, A)\n\nV = Rep[U₁×ℤ₂]((0, 0) => 2, (1, 1) => 1, (-1, 0) => 1)\ndim(V)\nA = randn(V*V, V)\ndim(A)\nconvert(Array, A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, the dim of a TensorMap returns the number of linearly independent components, i.e. the number of non-zero entries in the case of an abelian symmetry. Also note that we can use × (obtained as \\times+TAB) to combine different symmetry groups. The general space associated with symmetries is a GradedSpace, which is parametrized to the type of symmetry. For a group G, the fully specified type can be obtained as Rep[G], while for more general sectortypes I it can be constructed as Vect[I]. Furthermore, ℤ₂Space (also Z2Space as non-Unicode alternative) and U₁Space (or U1Space) are just convenient synonyms, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Rep[U₁](0=>3,1=>2,-1=>1) == U1Space(-1=>1,1=>2,0=>3)\nV = U₁Space(1=>2,0=>3,-1=>1)\nfor s in sectors(V)\n @show s, dim(V, s)\nend\nU₁Space(-1=>1,0=>3,1=>2) == GradedSpace(Irrep[U₁](1)=>2, Irrep[U₁](0)=>3, Irrep[U₁](-1)=>1)\nsupertype(GradedSpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that GradedSpace is not immediately parameterized by some group G, but actually by the set of irreducible representations of G, denoted as Irrep[G]. Indeed, GradedSpace also supports a grading that is derived from the fusion ring of a (unitary) pre-fusion category. Note furthermore that the order in which the charges and their corresponding subspace dimensionality are specified is irrelevant, and that the charges, henceforth called sectors (which is a more general name for charges or quantum numbers) are of a specific type, in this case Irrep[U₁] == U1Irrep. However, the Vect[I] constructor automatically converts the keys in the list of Pairs it receives to the correct type. Alternatively, we can directly create the sectors of the correct type and use the generic GradedSpace constructor. We can probe the subspace dimension of a certain sector s in a space V with dim(V, s). Finally, note that GradedSpace is also a subtype of EuclideanSpace, which implies that it still has the standard Euclidean inner product and we assume all representations to be unitary.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"TensorKit.jl also allows for non-abelian symmetries such as SU₂. In this case, the vector space is characterized via the spin quantum number (i.e. the irrep label of SU₂) for each of its subspaces, and is created using SU₂Space (or SU2Space or Rep[SU₂] or Vect[Irrep[SU₂]])","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = SU₂Space(0=>2,1/2=>1,1=>1)\ndim(V)\nV == Vect[Irrep[SU₂]](0=>2, 1=>1, 1//2=>1)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that now V has a two-dimensional subspace with spin zero, and two one-dimensional subspaces with spin 1/2 and spin 1. However, a subspace with spin j has an additional 2j+1 dimensional degeneracy on which the irreducible representation acts. This brings the total dimension to 2*1 + 1*2 + 1*3. Creating a tensor with SU₂ symmetry yields","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(V*V, V)\ndim(A)\nconvert(Array, A)\nnorm(A) ≈ norm(convert(Array, A))","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In this case, the full Array representation of the tensor has again many zeros, but it is less obvious to recognize the dense blocks, as there are additional zeros and the numbers in the original tensor data do not match with those in the Array. The reason is of course that the original tensor data now needs to be transformed with a construction known as fusion trees, which are made up out of the Clebsch-Gordan coefficients of the group. Indeed, note that the non-zero blocks are also no longer labeled by a list of sectors, but by pair of fusion trees. This will be explained further in the manual. However, the Clebsch-Gordan coefficients of the group are only needed to actually convert a tensor to an Array. For working with tensors with SU₂Space indices, e.g. contracting or factorizing them, the Clebsch-Gordan coefficients are never needed explicitly. Instead, recoupling relations are used to symbolically manipulate the basis of fusion trees, and this only requires what is known as the topological data of the group (or its representation theory).","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In fact, this formalism extends beyond the case of group representations on vector spaces, and can also deal with super vector spaces (to describe fermions) and more general (unitary) fusion categories. Support for all of these generalizations is present in TensorKit.jl. Indeed, all of these concepts will be explained throughout the remainder of this manual, including several details regarding their implementation. However, to just use tensors and their manipulations (contractions, factorizations, ...) in higher level algorithms (e.g. tensoer network algorithms), one does not need to know or understand most of these details, and one can immediately refer to the general interface of the TensorMap type, discussed on the last page. Adhering to this interface should yield code and algorithms that are oblivious to the underlying symmetries and can thus work with arbitrary symmetric tensors.","category":"page"},{"location":"lib/spaces/#Vector-spaces","page":"Vector spaces","title":"Vector spaces","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/spaces/#Type-hierarchy","page":"Vector spaces","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following types are defined to characterise vector spaces and their properties:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Field\nVectorSpace\nElementarySpace\nGeneralSpace\nCartesianSpace\nComplexSpace\nGradedSpace\nCompositeSpace\nProductSpace\nHomSpace","category":"page"},{"location":"lib/spaces/#TensorKit.Field","page":"Vector spaces","title":"TensorKit.Field","text":"abstract type Field end\n\nAbstract type at the top of the type hierarchy for denoting fields over which vector spaces (or more generally, linear categories) can be defined. Two common fields are ℝ and ℂ, representing the field of real or complex numbers respectively.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.VectorSpace","page":"Vector spaces","title":"TensorKit.VectorSpace","text":"abstract type VectorSpace end\n\nAbstract type at the top of the type hierarchy for denoting vector spaces, or, more accurately, 𝕜-linear categories. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ElementarySpace","page":"Vector spaces","title":"TensorKit.ElementarySpace","text":"abstract type ElementarySpace <: VectorSpace end\n\nElementary finite-dimensional vector space over a field that can be used as the index space corresponding to the indices of a tensor. ElementarySpace is a supertype for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.\n\nEvery elementary vector space should respond to the methods conj and dual, returning the complex conjugate space and the dual space respectively. The complex conjugate of the dual space is obtained as dual(conj(V)) === conj(dual(V)). These different spaces should be of the same type, so that a tensor can be defined as an element of a homogeneous tensor product of these spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.GeneralSpace","page":"Vector spaces","title":"TensorKit.GeneralSpace","text":"struct GeneralSpace{𝔽} <: ElementarySpace\n\nA finite-dimensional space over an arbitrary field 𝔽 without additional structure. It is thus characterized by its dimension, and whether or not it is the dual and/or conjugate space. For a real field 𝔽, the space and its conjugate are the same.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.CartesianSpace","page":"Vector spaces","title":"TensorKit.CartesianSpace","text":"struct CartesianSpace <: ElementarySpace\n\nA real Euclidean space ℝ^d, which is therefore self-dual. CartesianSpace has no additonal structure and is completely characterised by its dimension d. This is the vector space that is implicitly assumed in most of matrix algebra.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ComplexSpace","page":"Vector spaces","title":"TensorKit.ComplexSpace","text":"struct ComplexSpace <: ElementarySpace\n\nA standard complex vector space ℂ^d with Euclidean inner product and no additional structure. It is completely characterised by its dimension and whether its the normal space or its dual (which is canonically isomorphic to the conjugate space).\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.GradedSpace","page":"Vector spaces","title":"TensorKit.GradedSpace","text":"struct GradedSpace{I<:Sector, D} <: ElementarySpace\n dims::D\n dual::Bool\nend\n\nA complex Euclidean space with a direct sum structure corresponding to labels in a set I, the objects of which have the structure of a monoid with respect to a monoidal product ⊗. In practice, we restrict the label set to be a set of superselection sectors of type I<:Sector, e.g. the set of distinct irreps of a finite or compact group, or the isomorphism classes of simple objects of a unitary and pivotal (pre-)fusion category.\n\nHere dims represents the degeneracy or multiplicity of every sector.\n\nThe data structure D of dims will depend on the result Base.IteratorElsize(values(I)); if the result is of type HasLength or HasShape, dims will be stored in a NTuple{N,Int} with N = length(values(I)). This requires that a sector s::I can be transformed into an index via s == getindex(values(I), i) and i == findindex(values(I), s). If Base.IteratorElsize(values(I)) results IsInfinite() or SizeUnknown(), a SectorDict{I,Int} is used to store the non-zero degeneracy dimensions with the corresponding sector as key. The parameter D is hidden from the user and should typically be of no concern.\n\nThe concrete type GradedSpace{I,D} with correct D can be obtained as Vect[I], or if I == Irrep[G] for some G<:Group, as Rep[G].\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.CompositeSpace","page":"Vector spaces","title":"TensorKit.CompositeSpace","text":"abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end\n\nAbstract type for composite spaces that are defined in terms of a number of elementary vector spaces of a homogeneous type S<:ElementarySpace.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ProductSpace","page":"Vector spaces","title":"TensorKit.ProductSpace","text":"struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}\n\nA ProductSpace is a tensor product space of N vector spaces of type S<:ElementarySpace. Only tensor products between ElementarySpace objects of the same type are allowed.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.HomSpace","page":"Vector spaces","title":"TensorKit.HomSpace","text":"struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}\n codomain::P1\n domain::P2\nend\n\nRepresents the linear space of morphisms with codomain of type P1 and domain of type P2. Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"together with the following specific types for encoding the inner product structure of a space:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"InnerProductStyle","category":"page"},{"location":"lib/spaces/#TensorKit.InnerProductStyle","page":"Vector spaces","title":"TensorKit.InnerProductStyle","text":"InnerProductStyle(V::VectorSpace) -> ::InnerProductStyle\nInnerProductStyle(S::Type{<:VectorSpace}) -> ::InnerProductStyle\n\nReturn the type of inner product for vector spaces, which can be either\n\nNoInnerProduct(): no mapping from dual(V) to conj(V), i.e. no metric\nsubtype of HasInnerProduct: a metric exists, but no further support is implemented.\nEuclideanInnerProduct(): the metric is the identity, such that dual and conjugate spaces are isomorphic.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#Useful-constants","page":"Vector spaces","title":"Useful constants","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following constants are defined to easily create the concrete type of GradedSpace associated with a given type of sector.","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vect\nRep","category":"page"},{"location":"lib/spaces/#TensorKit.Vect","page":"Vector spaces","title":"TensorKit.Vect","text":"const Vect\n\nA constant of a singleton type used as Vect[I] with I<:Sector a type of sector, to construct or obtain the concrete type GradedSpace{I,D} instances without having to specify D.\n\n\n\n\n\n","category":"constant"},{"location":"lib/spaces/#TensorKit.Rep","page":"Vector spaces","title":"TensorKit.Rep","text":"const Rep\n\nA constant of a singleton type used as Rep[G] with G<:Group a type of group, to construct or obtain the concrete type GradedSpace{Irrep[G],D} instances without having to specify D. Note that Rep[G] == Vect[Irrep[G]].\n\nSee also Irrep and Vect.\n\n\n\n\n\n","category":"constant"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In this respect, there are also a number of type aliases for the GradedSpace types associated with the most common sectors, namely","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"const ZNSpace{N} = Vect[ZNIrrep{N}]\nconst Z2Space = ZNSpace{2}\nconst Z3Space = ZNSpace{3}\nconst Z4Space = ZNSpace{4}\nconst U1Space = Rep[U₁]\nconst CU1Space = Rep[CU₁]\nconst SU2Space = Rep[SU₂]\n\n# Unicode alternatives\nconst ℤ₂Space = Z2Space\nconst ℤ₃Space = Z3Space\nconst ℤ₄Space = Z4Space\nconst U₁Space = U1Space\nconst CU₁Space = CU1Space\nconst SU₂Space = SU2Space","category":"page"},{"location":"lib/spaces/#s_spacemethods","page":"Vector spaces","title":"Methods","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Methods often apply similar to e.g. spaces and corresponding tensors or tensor maps, e.g.:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"field\nsectortype\nsectors\nhassector\ndim(::VectorSpace)\ndim(::ElementarySpace, ::Sector)\nreduceddim\ndim(P::ProductSpace{<:ElementarySpace,N}, sector::NTuple{N,<:Sector}) where {N}\ndim(::HomSpace)\ndims\nblocksectors(P::ProductSpace{S,N}) where {S,N}\nblocksectors(::HomSpace)\nhasblock\nblockdim\nfusiontrees(P::ProductSpace{S,N}, blocksector::I) where {S,N,I}\nspace","category":"page"},{"location":"lib/spaces/#TensorKit.field","page":"Vector spaces","title":"TensorKit.field","text":"field(V::VectorSpace) -> Field\n\nReturn the field type over which a vector space is defined.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.sectortype","page":"Vector spaces","title":"TensorKit.sectortype","text":"sectortype(a) -> Type{<:Sector}\n\nReturn the type of sector over which object a (e.g. a representation space or a tensor) is defined. Also works in type domain.\n\n\n\n\n\nsectortype(::AbstractTensorMap) -> Type{I<:Sector}\nsectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}\n\nReturn the type of sector I of a tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.sectors","page":"Vector spaces","title":"TensorKit.sectors","text":"sectors(V::ElementarySpace)\n\nReturn an iterator over the different sectors of V.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.hassector","page":"Vector spaces","title":"TensorKit.hassector","text":"hassector(V::VectorSpace, a::Sector) -> Bool\n\nReturn whether a vector space V has a subspace corresponding to sector a with non-zero dimension, i.e. dim(V, a) > 0.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(V::VectorSpace) -> Int\n\nReturn the total dimension of the vector space V as an Int.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{ElementarySpace, Sector}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(V::ElementarySpace, s::Sector) -> Int\n\nReturn the degeneracy dimension corresponding to the sector s of the vector space V.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.reduceddim","page":"Vector spaces","title":"TensorKit.reduceddim","text":"reduceddim(V::ElementarySpace) -> Int\n\nReturn the sum of all degeneracy dimensions of the vector space V.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dim-Union{Tuple{N}, Tuple{ProductSpace{<:ElementarySpace, N}, NTuple{N, var\"#s2\"} where var\"#s2\"<:Sector}} where N","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}\n-> Int\n\nReturn the total degeneracy dimension corresponding to a tuple of sectors for each of the spaces in the tensor product, obtained as prod(dims(P, s))`.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{HomSpace}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(W::HomSpace)\n\nReturn the total dimension of a HomSpace, i.e. the number of linearly independent morphisms that can be constructed within this space.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.dims","page":"Vector spaces","title":"TensorKit.dims","text":"dims(::ProductSpace{S, N}) -> Dims{N} = NTuple{N, Int}\n\nReturn the dimensions of the spaces in the tensor product space as a tuple of integers.\n\n\n\n\n\ndims(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}\n-> Dims{N} = NTuple{N, Int}\n\nReturn the degeneracy dimensions corresponding to a tuple of sectors s for each of the spaces in the tensor product P.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.blocksectors-Union{Tuple{ProductSpace{S, N}}, Tuple{N}, Tuple{S}} where {S, N}","page":"Vector spaces","title":"TensorKit.blocksectors","text":"blocksectors(P::ProductSpace)\n\nReturn an iterator over the different unique coupled sector labels, i.e. the different fusion outputs that can be obtained by fusing the sectors present in the different spaces that make up the ProductSpace instance.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.blocksectors-Tuple{HomSpace}","page":"Vector spaces","title":"TensorKit.blocksectors","text":"blocksectors(W::HomSpace)\n\nReturn an iterator over the different unique coupled sector labels, i.e. the intersection of the different fusion outputs that can be obtained by fusing the sectors present in the domain, as well as from the codomain.\n\nSee also hasblock.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.hasblock","page":"Vector spaces","title":"TensorKit.hasblock","text":"hasblock(P::ProductSpace, c::Sector)\n\nQuery whether a coupled sector c appears with nonzero dimension in P, i.e. whether blockdim(P, c) > 0.\n\nSee also blockdim and blocksectors.\n\n\n\n\n\nhasblock(W::HomSpace, c::Sector)\n\nQuery whether a coupled sector c appears in both the codomain and domain of W.\n\nSee also blocksectors.\n\n\n\n\n\nhasblock(t::AbstractTensorMap, c::Sector) -> Bool\n\nVerify whether a tensor has a block corresponding to a coupled sector c.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.blockdim","page":"Vector spaces","title":"TensorKit.blockdim","text":"blockdim(P::ProductSpace, c::Sector)\n\nReturn the total dimension of a coupled sector c in the product space, by summing over all dim(P, s) for all tuples of sectors s::NTuple{N, <:Sector} that can fuse to c, counted with the correct multiplicity (i.e. number of ways in which s can fuse to c).\n\nSee also hasblock and blocksectors.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.fusiontrees-Union{Tuple{I}, Tuple{N}, Tuple{S}, Tuple{ProductSpace{S, N}, I}} where {S, N, I}","page":"Vector spaces","title":"TensorKit.fusiontrees","text":"fusiontrees(P::ProductSpace, blocksector::Sector)\n\nReturn an iterator over all fusion trees that can be formed by fusing the sectors present in the different spaces that make up the ProductSpace instance into the coupled sector blocksector.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.space","page":"Vector spaces","title":"TensorKit.space","text":"space(a) -> VectorSpace\n\nReturn the vector space associated to object a.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following methods act specifically on ElementarySpace spaces:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"isdual\ndual\nconj\nflip\n⊕\nzero(::ElementarySpace)\noneunit\nsupremum\ninfimum","category":"page"},{"location":"lib/spaces/#TensorKit.isdual","page":"Vector spaces","title":"TensorKit.isdual","text":"isdual(V::ElementarySpace) -> Bool\n\nReturn wether an ElementarySpace V is normal or rather a dual space. Always returns false for spaces where V == dual(V).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dual","page":"Vector spaces","title":"TensorKitSectors.dual","text":"dual(V::VectorSpace) -> VectorSpace\n\nReturn the dual space of V; also obtained via V'. This should satisfy dual(dual(V)) == V. It is assumed that typeof(V) == typeof(V').\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#Base.conj","page":"Vector spaces","title":"Base.conj","text":"conj(V::S) where {S<:ElementarySpace} -> S\n\nReturn the conjugate space of V. This should satisfy conj(conj(V)) == V.\n\nFor field(V)==ℝ, conj(V) == V. It is assumed that typeof(V) == typeof(conj(V)).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.flip","page":"Vector spaces","title":"TensorKit.flip","text":"flip(V::S) where {S<:ElementarySpace} -> S\n\nReturn a single vector space of type S that has the same value of isdual as dual(V), but yet is isomorphic to V rather than to dual(V). The spaces flip(V) and dual(V) only differ in the case of GradedSpace{I}.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.:⊕","page":"Vector spaces","title":"TensorKit.:⊕","text":"⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\noplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the direct sum sum of the spaces V₁, V₂, ... Note that all the individual spaces should have the same value for isdual, as otherwise the direct sum is not defined.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#Base.zero-Tuple{ElementarySpace}","page":"Vector spaces","title":"Base.zero","text":"zero(V::S) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. \n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#Base.oneunit","page":"Vector spaces","title":"Base.oneunit","text":"oneunit(V::S) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from one(V::S), which returns the empty product space ProductSpace{S,0}(()).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.supremum","page":"Vector spaces","title":"TensorKit.supremum","text":"supremum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)\n\nReturn the supremum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≿ V₁, V ≿ V₂, ... and no other W ≺ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.infimum","page":"Vector spaces","title":"TensorKit.infimum","text":"infimum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)\n\nReturn the infimum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≾ V₁, V ≾ V₂, ... and no other W ≻ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"while the following also work on both ElementarySpace and ProductSpace","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"one(::VectorSpace)\nfuse\n⊗(::VectorSpace, ::VectorSpace)\n⊠(::VectorSpace, ::VectorSpace)\nismonomorphic\nisepimorphic\nisisomorphic\ninsertunit","category":"page"},{"location":"lib/spaces/#Base.one-Tuple{VectorSpace}","page":"Vector spaces","title":"Base.one","text":"one(::S) where {S<:ElementarySpace} -> ProductSpace{S, 0}\none(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S, 0}\n\nReturn a tensor product of zero spaces of type S, i.e. this is the unit object under the tensor product operation, such that V ⊗ one(V) == V.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.fuse","page":"Vector spaces","title":"TensorKit.fuse","text":"fuse(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\nfuse(P::ProductSpace{S}) where {S<:ElementarySpace} -> S\n\nReturn a single vector space of type S that is isomorphic to the fusion product of the individual spaces V₁, V₂, ..., or the spaces contained in P.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.:⊗-Tuple{VectorSpace, VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.:⊗","text":"⊗(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\n\nCreate a ProductSpace{S}(V₁, V₂, V₃...) representing the tensor product of several elementary vector spaces. For convience, Julia's regular multiplication operator * applied to vector spaces has the same effect.\n\nThe tensor product structure is preserved, see fuse for returning a single elementary space of type S that is isomorphic to this tensor product.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.:⊠-Tuple{VectorSpace, VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.:⊠","text":"⊠(V₁::VectorSpace, V₂::VectorSpace)\n\nGiven two vector spaces V₁ and V₂ (ElementarySpace or ProductSpace), or thus, objects of corresponding fusion categories C₁ and C₂, V₁ V₂ constructs the Deligne tensor product, an object in C₁ C₂ which is the natural tensor product of those categories. In particular, the corresponding type of sectors (simple objects) is given by sectortype(V₁ ⊠ V₂) == sectortype(V₁) ⊠ sectortype(V₂) and can be thought of as a tuple of the individual sectors.\n\nThe Deligne tensor product also works in the type domain and for sectors and tensors. For group representations, we have Rep[G₁] ⊠ Rep[G₂] == Rep[G₁ × G₂], i.e. these are the natural representation spaces of the direct product of two groups.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.ismonomorphic","page":"Vector spaces","title":"TensorKit.ismonomorphic","text":"ismonomorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≾ V₂\n\nReturn whether there exist monomorphisms from V₁ to V₂, i.e. 'injective' morphisms with left inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.isepimorphic","page":"Vector spaces","title":"TensorKit.isepimorphic","text":"isepimorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≿ V₂\n\nReturn whether there exist epimorphisms from V₁ to V₂, i.e. 'surjective' morphisms with right inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.isisomorphic","page":"Vector spaces","title":"TensorKit.isisomorphic","text":"isisomorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≅ V₂\n\nReturn if V₁ and V₂ are isomorphic, meaning that there exists isomorphisms from V₁ to V₂, i.e. morphisms with left and right inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.insertunit","page":"Vector spaces","title":"TensorKit.insertunit","text":"insertunit(P::ProductSpace, i::Int = length(P)+1; dual = false, conj = false)\n\nFor P::ProductSpace{S,N}, this adds an extra tensor product factor at position 1 <= i <= N+1 (last position by default) which is just the S-equivalent of the underlying field of scalars, i.e. oneunit(S). With the keyword arguments, one can choose to insert the conjugated or dual space instead, which are all isomorphic to the field of scalars.\n\nThis operation can be undone by removeunit.\n\n\n\n\n\ninsertunit(W::HomSpace, i::Int=ndims(W) + 1; conj=false, dual=false, preferdomain=false)\n\nInsert a trivial vector space, isomorphic to the underlying field, at position i. Whenever i == numout(W), the ambiguity to determine whether this space is added in the domain or codomain is controlled by preferdomain.\n\n\n\n\n\ninsertunit(tsrc::AbstractTensorMap, i::Int=numind(t) + 1;\n conj=false, dual=false, preferdomain=false, copy=false) -> tdst\n\nInsert a trivial vector space, isomorphic to the underlying field, at position i. Whenever i == numout(W), the ambiguity to determine whether this space is added in the domain or codomain is controlled by preferdomain.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There are also specific methods for HomSpace instances, that are used in determining the resuling HomSpace after applying certain tensor operations.","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"TensorKit.permute(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂}\nTensorKit.select(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂}\nTensorKit.compose(::HomSpace{S}, ::HomSpace{S}) where {S}","category":"page"},{"location":"lib/spaces/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {S, N₁, N₂}","page":"Vector spaces","title":"TensorKit.permute","text":"permute(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})\n\nReturn the HomSpace obtained by permuting the indices of the domain and codomain of W according to the permutation p₁ and p₂ respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.select-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {S, N₁, N₂}","page":"Vector spaces","title":"TensorKit.select","text":"select(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})\n\nReturn the HomSpace obtained by a selection from the domain and codomain of W according to the indices in p₁ and p₂ respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.compose-Union{Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}}} where S","page":"Vector spaces","title":"TensorKit.compose","text":"compose(W::HomSpace, V::HomSpace)\n\nObtain the HomSpace that is obtained from composing the morphisms in W and V. For this to be possible, the domain of W must match the codomain of V.\n\n\n\n\n\n","category":"method"},{"location":"man/intro/#s_intro","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Before providing a typical \"user guide\" and discussing the implementation of TensorKit.jl on the next pages, let us discuss some of the rationale behind this package.","category":"page"},{"location":"man/intro/#ss_whatistensor","page":"Introduction","title":"What is a tensor?","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"At the very start we should ponder about the most suitable and sufficiently general definition of a tensor. A good starting point is the following:","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor t is an element from the tensor product of N vector spaces V_1 V_2 V_N, where N is referred to as the rank or order of the tensor, i.e.\nt V_1 V_2 V_N","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"If you think of a tensor as an object with indices, a rank N tensor has N indices where every index is associated with the corresponding vector space in that it labels a particular basis in that space. We will return to index notation at the very end of this manual.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"As the tensor product of vector spaces is itself a vector space, this implies that a tensor behaves as a vector, i.e. tensors from the same tensor product space can be added and multiplied by scalars. The tensor product is only defined for vector spaces over the same field of scalars, e.g. there is no meaning in ℝ^5 ℂ^3. When all the vector spaces in the tensor product have an inner product, this also implies an inner product for the tensor product space. It is hence clear that the different vector spaces in the tensor product should have some form of homogeneity in their structure, yet they do not need to be all equal and can e.g. have different dimensions. It goes without saying that defining the vector spaces and their properties will be an important part of the definition of a tensor. As a consequence, this also constitutes a significant part of the implementation, and is discussed in the section on Vector spaces.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Aside from the interpretation of a tensor as a vector, we also want to interpret it as a matrix (or more correctly, a linear map) in order to decompose tensors using linear algebra factorisations (e.g. eigenvalue or singular value decomposition). Henceforth, we use the term \"tensor map\" as follows:","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor map t is a linear map from a source or domain W_1 W_2 W_N_2 to a target or codomain V_1 V_2 V_N_1, i.e.\ntW_1 W_2 W_N_2 V_1 V_2 V_N_1","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor of rank N is then just a special case of a tensor map with N_1 = N and N_2 = 0. A contraction between two tensors is just a composition of linear maps (i.e. matrix multiplication), where the contracted indices correspond to the domain of the first tensor and the codomain of the second tensor.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"In order to allow for arbitrary tensor contractions or decompositions, we need to be able to reorganise which vector spaces appear in the domain and the codomain of the tensor map, and in which order. This amounts to defining canonical isomorphisms between the different ways to order and partition the tensor indices (i.e. the vector spaces). For example, a linear map W V is often denoted as a rank 2 tensor in V W^*, where W^* corresponds to the dual space of W. This simple example introduces two new concepts.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Typical vector spaces can appear in the domain and codomain in different related forms, e.g. as normal space or dual space. In fact, the most generic case is that every vector space V has associated with it a dual space V^*, a conjugate space overlineV and a conjugate dual space overlineV^*. The four different vector spaces V, V^*, overlineV and overlineV^* correspond to the representation spaces of respectively the fundamental, dual or contragredient, complex conjugate and dual complex conjugate representation of the general linear group mathsfGL(V). In index notation these spaces are denoted with respectively contravariant (upper), covariant (lower), dotted contravariant and dotted covariant indices.\nFor real vector spaces, the conjugate (dual) space is identical to the normal (dual) space and we only have upper and lower indices, i.e. this is the setting of e.g. general relativity. For (complex) vector spaces with a sesquilinear inner product overlineV V ℂ, the inner product allows to define an isomorphism from the conjugate space to the dual space (known as Riesz representation theorem in the more general context of Hilbert spaces).\nIn particular, in spaces with a Euclidean inner product (the setting of e.g. quantum mechanics), the conjugate and dual space are naturally isomorphic (because the dual and conjugate representation of the unitary group are the same). Again we only need upper and lower indices (or kets and bras).\nFinally, in ℝ^d with a Euclidean inner product, these four different spaces are all equivalent and we only need one type of index. The space is completely characterized by its dimension d. This is the setting of much of classical mechanics and we refer to such tensors as cartesian tensors and the corresponding space as cartesian space. These are the tensors that can equally well be represented as multidimensional arrays (i.e. using some AbstractArray{<:Real,N} in Julia) without loss of structure.\nThe implementation of all of this is discussed in Vector spaces.\nIn the generic case, the identification between maps W V and tensors in V W^* is not an equivalence but an isomorphism, which needs to be defined. Similarly, there is an isomorphism between between V W and W V that can be non-trivial (e.g. in the case of fermions / super vector spaces). The correct formalism here is provided by theory of monoidal categories, which is introduced on the next page. Nonetheless, we try to hide these canonical isomorphisms from the user wherever possible, and one does not need to know category theory to be able to use this package.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"This brings us to our final (yet formal) definition","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor (map) is a homomorphism between two objects from the category mathbfVect (or some subcategory thereof). In practice, this will be mathbfFinVect, the category of finite dimensional vector spaces. More generally even, our concept of a tensor makes sense, in principle, for any linear (a.k.a. mathbfVect-enriched) monoidal category. We refer to the next page on \"Monoidal categories and their properties\".","category":"page"},{"location":"man/intro/#ss_symmetries","page":"Introduction","title":"Symmetries and block sparsity","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Physical problems often have some symmetry, i.e. the setup is invariant under the action of a group mathsfG which acts on the vector spaces V in the problem according to a certain representation. Having quantum mechanics in mind, TensorKit.jl is so far restricted to unitary representations. A general representation space V can be specified as the number of times every irreducible representation (irrep) a of mathsfG appears, i.e.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"V = bigoplus_a ℂ^n_a R_a","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"with R_a the space associated with irrep a of mathsfG, which itself has dimension d_a (often called the quantum dimension), and n_a the number of times this irrep appears in V. If the unitary irrep a for g mathsfG is given by u_a(g), then there exists a specific basis for V such that the group action of mathsfG on V is given by the unitary representation","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"u(g) = bigoplus_a 𝟙_n_a u_a(g)","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"with 𝟙_n_a the n_a n_a identity matrix. The total dimension of V is given by _a n_a d_a.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"The reason for implementing symmetries is to exploit the computation and memory gains resulting from restricting to tensor maps tW_1 W_2 W_N_2 V_1 V_2 V_N_1 that are equivariant under the symmetry, i.e. that act as intertwiners between the symmetry action on the domain and the codomain. Indeed, such tensors should be block diagonal because of Schur's lemma, but only after we couple the individual irreps in the spaces W_i to a joint irrep, which is then again split into the individual irreps of the spaces V_i. The basis change from the tensor product of irreps in the (co)domain to the joint irrep is implemented by a sequence of Clebsch–Gordan coefficients, also known as a fusion (or splitting) tree. We implement the necessary machinery to manipulate these fusion trees under index permutations and repartitions for arbitrary groups mathsfG. In particular, this fits with the formalism of monoidal categories, and more specifically fusion categories, and only requires the topological data of the group, i.e. the fusion rules of the irreps, their quantum dimensions and the F-symbol (6j-symbol or more precisely Racah's W-symbol in the case of mathsfSU_2). In particular, we don't actually need the Clebsch–Gordan coefficients themselves (but they can be useful for checking purposes).","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Hence, a second major part of TensorKit.jl is the interface and implementation for specifying symmetries, and further details are provided in Sectors, representation spaces and fusion trees.","category":"page"},{"location":"lib/sectors/#Symmetry-sectors-and-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/sectors/#Type-hierarchy","page":"Symmetry sectors and fusion trees","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Sector\nSectorValues\nFusionStyle\nBraidingStyle\nAbstractIrrep\nTrivial\nZNIrrep\nU1Irrep\nSU2Irrep\nCU1Irrep\nProductSector\nFermionParity\nFermionNumber\nFermionSpin\nFibonacciAnyon\nIsingAnyon","category":"page"},{"location":"lib/sectors/#TensorKitSectors.Sector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Sector","text":"abstract type Sector end\n\nAbstract type for representing the (isomorphism classes of) simple objects in (unitary and pivotal) (pre-)fusion categories, e.g. the irreducible representations of a finite or compact group. Subtypes I<:Sector as the set of labels of a GradedSpace.\n\nEvery new I<:Sector should implement the following methods:\n\none(::Type{I}): unit element of I\nconj(a::I): a, conjugate or dual label of a\n⊗(a::I, b::I): iterable with unique fusion outputs of a b (i.e. don't repeat in case of multiplicities)\nNsymbol(a::I, b::I, c::I): number of times c appears in a ⊗ b, i.e. the multiplicity\nFusionStyle(::Type{I}): UniqueFusion(), SimpleFusion() or GenericFusion()\nBraidingStyle(::Type{I}): Bosonic(), Fermionic(), Anyonic(), ...\nFsymbol(a::I, b::I, c::I, d::I, e::I, f::I): F-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\nRsymbol(a::I, b::I, c::I): R-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\n\nand optionally\n\ndim(a::I): quantum dimension of sector a\nfrobeniusschur(a::I): Frobenius-Schur indicator of a\nBsymbol(a::I, b::I, c::I): B-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\ntwist(a::I) -> twist of sector a\n\nFurthermore, iterate and Base.IteratorSize should be made to work for the singleton type SectorValues{I}.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.SectorValues","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.SectorValues","text":"struct SectorValues{I<:Sector}\n\nSingleton type to represent an iterator over the possible values of type I, whose instance is obtained as values(I). For a new I::Sector, the following should be defined\n\nBase.iterate(::SectorValues{I}[, state]): iterate over the values\nBase.IteratorSize(::Type{SectorValues{I}}): HasLenght(), SizeUnkown() or IsInfinite() depending on whether the number of values of type I is finite (and sufficiently small) or infinite; for a large number of values, SizeUnknown() is recommend because this will trigger the use of GenericGradedSpace.\n\nIf IteratorSize(I) == HasLength(), also the following must be implemented:\n\nBase.length(::SectorValues{I}): the number of different values\nBase.getindex(::SectorValues{I}, i::Int): a mapping between an index i and an instance of I\nfindindex(::SectorValues{I}, c::I): reverse mapping between a value c::I and an index i::Integer ∈ 1:length(values(I))\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FusionStyle","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FusionStyle","text":"FusionStyle(::Sector)\nFusionStyle(I::Type{<:Sector})\n\nTrait to describe the fusion behavior of sectors of type I, which can be either\n\nUniqueFusion(): single fusion output when fusing two sectors;\nSimpleFusion(): multiple outputs, but every output occurs at most one, also known as multiplicity-free (e.g. irreps of SU(2));\nGenericFusion(): multiple outputs that can occur more than once (e.g. irreps of SU(3)).\n\nThere is an abstract supertype MultipleFusion of which both SimpleFusion and GenericFusion are subtypes. Furthermore, there is a type alias MultiplicityFreeFusion for those fusion types which do not require muliplicity labels, i.e. MultiplicityFreeFusion = Union{UniqueFusion,SimpleFusion}.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.BraidingStyle","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.BraidingStyle","text":"BraidingStyle(::Sector) -> ::BraidingStyle\nBraidingStyle(I::Type{<:Sector}) -> ::BraidingStyle\n\nReturn the type of braiding and twist behavior of sectors of type I, which can be either\n\nBosonic(): symmetric braiding with trivial twist (i.e. identity)\nFermionic(): symmetric braiding with non-trivial twist (squares to identity)\nAnyonic(): general R_(ab)^c phase or matrix (depending on SimpleFusion or GenericFusion fusion) and arbitrary twists\n\nNote that Bosonic and Fermionic are subtypes of SymmetricBraiding, which means that braids are in fact equivalent to crossings (i.e. braiding twice is an identity: isone(Rsymbol(b,a,c)*Rsymbol(a,b,c)) == true) and permutations are uniquely defined.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.AbstractIrrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.AbstractIrrep","text":"abstract type AbstractIrrep{G<:Group} <: Sector end\n\nAbstract supertype for sectors which corresponds to irreps (irreducible representations) of a group G. As we assume unitary representations, these would be finite groups or compact Lie groups. Note that this could also include projective rather than linear representations.\n\nActual concrete implementations of those irreps can be obtained as Irrep[G], or via their actual name, which generically takes the form (asciiG)Irrep, i.e. the ASCII spelling of the group name followed by Irrep.\n\nAll irreps have BraidingStyle equal to Bosonic() and thus trivial twists.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.Trivial","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Trivial","text":"Trivial\n\nSingleton type to represent the trivial sector, i.e. the trivial representation of the trivial group. This is equivalent to Rep[ℤ₁], or the unit object of the category Vect of ordinary vector spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.ZNIrrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.ZNIrrep","text":"struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}\nZNIrrep{N}(n::Integer)\nIrrep[ℤ{N}](n::Integer)\n\nRepresents irreps of the group ℤ_N for some value of N<64. (We need 2*(N-1) <= 127 in order for a ⊗ b to work correctly.) For N equals 2, 3 or 4, ℤ{N} can be replaced by ℤ₂, ℤ₃, ℤ₄. An arbitrary Integer n can be provided to the constructor, but only the value mod(n, N) is relevant.\n\nFields\n\nn::Int8: the integer label of the irrep, modulo N.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.U1Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.U1Irrep","text":"struct U1Irrep <: AbstractIrrep{U₁}\nU1Irrep(charge::Real)\nIrrep[U₁](charge::Real)\n\nRepresents irreps of the group U₁. The irrep is labelled by a charge, which should be an integer for a linear representation. However, it is often useful to allow half integers to represent irreps of U₁ subgroups of SU₂, such as the Sz of spin-1/2 system. Hence, the charge is stored as a HalfInt from the package HalfIntegers.jl, but can be entered as arbitrary Real. The sequence of the charges is: 0, 1/2, -1/2, 1, -1, ...\n\nFields\n\ncharge::HalfInt: the label of the irrep, which can be any half integer.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.SU2Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.SU2Irrep","text":"struct SU2Irrep <: AbstractIrrep{SU₂}\nSU2Irrep(j::Real)\nIrrep[SU₂](j::Real)\n\nRepresents irreps of the group SU₂. The irrep is labelled by a half integer j which can be entered as an abitrary Real, but is stored as a HalfInt from the HalfIntegers.jl package.\n\nFields\n\nj::HalfInt: the label of the irrep, which can be any non-negative half integer.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.CU1Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.CU1Irrep","text":"struct CU1Irrep <: AbstractIrrep{CU₁}\nCU1Irrep(j, s = ifelse(j>zero(j), 2, 0))\nIrrep[CU₁](j, s = ifelse(j>zero(j), 2, 0))\n\nRepresents irreps of the group U₁ C (U₁ and charge conjugation or reflection), which is also known as just O₂. \n\nFields\n\nj::HalfInt: the value of the U₁ charge.\ns::Int: the representation of charge conjugation.\n\nThey can take values:\n\nif j == 0, s = 0 (trivial charge conjugation) or s = 1 (non-trivial charge conjugation)\nif j > 0, s = 2 (two-dimensional representation)\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.ProductSector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.ProductSector","text":"ProductSector{T<:SectorTuple}\n\nRepresents the Deligne tensor product of sectors. The type parameter T is a tuple of the component sectors. The recommended way to construct a ProductSector is using the deligneproduct (⊠) operator on the components.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionParity","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionParity","text":"FermionParity <: Sector\n\nRepresents sectors with fermion parity. The fermion parity is a ℤ₂ quantum number that yields an additional sign when two odd fermions are exchanged.\n\nFields\n\nisodd::Bool: indicates whether the fermion parity is odd (true) or even (false).\n\nSee also: FermionNumber, FermionSpin\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionNumber","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionNumber","text":"const FermionNumber = U1Irrep ⊠ FermionParity\nFermionNumber(a::Int)\n\nRepresents the fermion number as the direct product of a U₁ irrep a and a fermion parity, with the restriction that the fermion parity is odd if and only if a is odd.\n\nSee also: U1Irrep, FermionParity\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionSpin","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionSpin","text":"const FermionSpin = SU2Irrep ⊠ FermionParity\nFermionSpin(j::Real)\n\nRepresents the fermion spin as the direct product of a SU₂ irrep j and a fermion parity, with the restriction that the fermion parity is odd if 2 * j is odd.\n\nSee also: SU2Irrep, FermionParity\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FibonacciAnyon","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FibonacciAnyon","text":"struct FibonacciAnyon <: Sector\nFibonacciAnyon(s::Symbol)\n\nRepresents the anyons of the Fibonacci modular fusion category. It can take two values, corresponding to the trivial sector FibonacciAnyon(:I) and the non-trivial sector FibonacciAnyon(:τ) with fusion rules τ τ = 1 τ.\n\nFields\n\nisone::Bool: indicates whether the sector corresponds the to trivial anyon :I (true), or the non-trivial anyon :τ (false).\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.IsingAnyon","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.IsingAnyon","text":"struct IsingAnyon <: Sector\nIsingAnyon(s::Symbol)\n\nRepresents the anyons of the Ising modular fusion category. It can take three values, corresponding to the trivial sector IsingAnyon(:I) and the non-trivial sectors IsingAnyon(:σ) and IsingAnyon(:ψ), with fusion rules ψ ψ = 1, σ ψ = σ, and σ σ = 1 ψ.\n\nFields\n\ns::Symbol: the label of the represented anyon, which can be :I, :σ, or :ψ.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#Useful-constants","page":"Symmetry sectors and fusion trees","title":"Useful constants","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Irrep","category":"page"},{"location":"lib/sectors/#TensorKitSectors.Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Irrep","text":"const Irrep\n\nA constant of a singleton type used as Irrep[G] with G<:Group a type of group, to construct or obtain a concrete subtype of AbstractIrrep{G} that implements the data structure used to represent irreducible representations of the group G.\n\n\n\n\n\n","category":"constant"},{"location":"lib/sectors/#Methods-for-defining-and-characterizing-Sector-subtypes","page":"Symmetry sectors and fusion trees","title":"Methods for defining and characterizing Sector subtypes","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Base.one(::Sector)\ndual(::Sector)\nNsymbol\n⊗\nFsymbol\nRsymbol\nBsymbol\ndim(::Sector)\nfrobeniusschur\ntwist(::Sector)\nBase.isreal(::Type{<:Sector})\nTensorKitSectors.sectorscalartype\ndeligneproduct(::Sector, ::Sector)","category":"page"},{"location":"lib/sectors/#Base.one-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"Base.one","text":"one(::Sector) -> Sector\none(::Type{<:Sector}) -> Sector\n\nReturn the unit element within this type of sector.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.dual-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.dual","text":"dual(a::Sector) -> Sector\n\nReturn the conjugate label conj(a).\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.Nsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Nsymbol","text":"Nsymbol(a::I, b::I, c::I) where {I<:Sector} -> Integer\n\nReturn an Integer representing the number of times c appears in the fusion product a ⊗ b. Could be a Bool if FusionStyle(I) == UniqueFusion() or SimpleFusion().\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.:⊗","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.:⊗","text":"⊗(a::I, b::I...) where {I<:Sector}\notimes(a::I, b::I...) where {I<:Sector}\n\nReturn an iterable of elements of c::I that appear in the fusion product a ⊗ b.\n\nNote that every element c should appear at most once, fusion degeneracies (if FusionStyle(I) == GenericFusion()) should be accessed via Nsymbol(a, b, c).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Fsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Fsymbol","text":"Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:Sector}\n\nReturn the F-symbol F^abc_d that associates the two different fusion orders of sectors a, b and c into an ouput sector d, using either an intermediate sector a b e or b c f:\n\na-<-μ-<-e-<-ν-<-d a-<-λ-<-d\n ∨ ∨ -> Fsymbol(a,b,c,d,e,f)[μ,ν,κ,λ] ∨\n b c f\n v\n b-<-κ\n ∨\n c\n\nIf FusionStyle(I) is UniqueFusion or SimpleFusion, the F-symbol is a number. Otherwise it is a rank 4 array of size (Nsymbol(a, b, e), Nsymbol(e, c, d), Nsymbol(b, c, f), Nsymbol(a, f, d)).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Rsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Rsymbol","text":"Rsymbol(a::I, b::I, c::I) where {I<:Sector}\n\nReturns the R-symbol R^ab_c that maps between c a b and c b a as in\n\na -<-μ-<- c b -<-ν-<- c\n ∨ -> Rsymbol(a,b,c)[μ,ν] v\n b a\n\nIf FusionStyle(I) is UniqueFusion() or SimpleFusion(), the R-symbol is a number. Otherwise it is a square matrix with row and column size Nsymbol(a,b,c) == Nsymbol(b,a,c).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Bsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Bsymbol","text":"Bsymbol(a::I, b::I, c::I) where {I<:Sector}\n\nReturn the value of B^ab_c which appears in transforming a splitting vertex into a fusion vertex using the transformation\n\na -<-μ-<- c a -<-ν-<- c\n ∨ -> √(dim(c)/dim(a)) * Bsymbol(a,b,c)[μ,ν] ∧\n b dual(b)\n\nIf FusionStyle(I) is UniqueFusion() or SimpleFusion(), the B-symbol is a number. Otherwise it is a square matrix with row and column size Nsymbol(a, b, c) == Nsymbol(c, dual(b), a).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.dim-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.dim","text":"dim(a::Sector)\n\nReturn the (quantum) dimension of the sector a.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.frobeniusschur","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.frobeniusschur","text":"frobeniusschur(a::Sector)\n\nReturn the Frobenius-Schur indicator of a sector a.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.twist-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.twist","text":"twist(a::Sector)\n\nReturn the twist of a sector a\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#Base.isreal-Tuple{Type{<:Sector}}","page":"Symmetry sectors and fusion trees","title":"Base.isreal","text":"isreal(::Type{<:Sector}) -> Bool\n\nReturn whether the topological data (Fsymbol, Rsymbol) of the sector is real or not (in which case it is complex).\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.sectorscalartype","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.sectorscalartype","text":"sectorscalartype(I::Type{<:Sector}) -> Type\n\nReturn the scalar type of the topological data (Fsymbol, Rsymbol) of the sector I.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.deligneproduct-Tuple{Sector, Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.deligneproduct","text":"⊠(s₁::Sector, s₂::Sector)\ndeligneproduct(s₁::Sector, s₂::Sector)\n\nGiven two sectors s₁ and s₂, which label an isomorphism class of simple objects in a fusion category C₁ and C₂, s1 ⊠ s2 (obtained as \\boxtimes+TAB) labels the isomorphism class of simple objects in the Deligne tensor product category C₁ C₂.\n\nThe Deligne tensor product also works in the type domain and for spaces and tensors. For group representations, we have Irrep[G₁] ⊠ Irrep[G₂] == Irrep[G₁ × G₂].\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Compile all revelant methods for a sector:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"TensorKitSectors.precompile_sector","category":"page"},{"location":"lib/sectors/#TensorKitSectors.precompile_sector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.precompile_sector","text":"precompile_sector(I::Type{<:Sector})\n\nPrecompile common methods for the given sector type.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Types-and-methods-for-groups","page":"Symmetry sectors and fusion trees","title":"Types and methods for groups","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Types and constants:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"# TODO: add documentation for the following types\nGroup\nTensorKitSectors.AbelianGroup\nU₁\nℤ{N} where N\nSU{N} where N\nconst SU₂ = SU{2}\nProductGroup","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Specific methods:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"×","category":"page"},{"location":"lib/sectors/#TensorKitSectors.:×","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.:×","text":"×(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}\ntimes(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}\n\nConstruct the direct product of a (list of) groups.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Methods-for-defining-and-generating-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Methods for defining and generating fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"FusionTree\nfusiontrees(uncoupled::NTuple{N,I}, coupled::I,\n isdual::NTuple{N,Bool}) where {N,I<:Sector}","category":"page"},{"location":"lib/sectors/#TensorKit.FusionTree","page":"Symmetry sectors and fusion trees","title":"TensorKit.FusionTree","text":"struct FusionTree{I, N, M, L}\n\nRepresents a fusion tree of sectors of type I<:Sector, fusing (or splitting) N uncoupled sectors to a coupled sector. It actually represents a splitting tree, but fusion tree is a more common term.\n\nFields\n\nuncoupled::NTuple{N,I}: the uncoupled sectors coming out of the splitting tree, before the possible 𝑍 isomorphism (see isdual).\ncoupled::I: the coupled sector.\nisdual::NTuple{N,Bool}: indicates whether a 𝑍 isomorphism is present (true) or not (false) for each uncoupled sector.\ninnerlines::NTuple{M,I}: the labels of the M=max(0, N-2) inner lines of the splitting tree.\nvertices::NTuple{L,Int}: the integer values of the L=max(0, N-1) vertices of the splitting tree. If FusionStyle(I) isa MultiplicityFreeFusion, then vertices is simply equal to the constant value ntuple(n->1, L).\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKit.fusiontrees-Union{Tuple{I}, Tuple{N}, Tuple{NTuple{N, I}, I, NTuple{N, Bool}}} where {N, I<:Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKit.fusiontrees","text":"fusiontrees(uncoupled::NTuple{N,I}[,\n coupled::I=one(I)[, isdual::NTuple{N,Bool}=ntuple(n -> false, length(uncoupled))]])\n where {N,I<:Sector} -> FusionTreeIterator{I,N,I}\n\nReturn an iterator over all fusion trees with a given coupled sector label coupled and uncoupled sector labels and isomorphisms uncoupled and isdual respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#Methods-for-manipulating-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Methods for manipulating fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"For manipulating single fusion trees, the following internal methods are defined:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"insertat\nsplit\nmerge\nelementary_trace\nplanar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}\nartin_braid\nbraid(f::FusionTree{I,N}, levels::NTuple{N,Int}, p::NTuple{N,Int}) where {I<:Sector,N}\npermute(f::FusionTree{I,N}, p::NTuple{N,Int}) where {I<:Sector,N}","category":"page"},{"location":"lib/sectors/#TensorKit.insertat","page":"Symmetry sectors and fusion trees","title":"TensorKit.insertat","text":"insertat(f::FusionTree{I, N₁}, i::Int, f₂::FusionTree{I, N₂})\n-> <:AbstractDict{<:FusionTree{I, N₁+N₂-1}, <:Number}\n\nAttach a fusion tree f₂ to the uncoupled leg i of the fusion tree f₁ and bring it into a linear combination of fusion trees in standard form. This requires that f₂.coupled == f₁.uncoupled[i] and f₁.isdual[i] == false.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.split","page":"Symmetry sectors and fusion trees","title":"TensorKit.split","text":"split(f::FusionTree{I, N}, M::Int)\n-> (::FusionTree{I, M}, ::FusionTree{I, N-M+1})\n\nSplit a fusion tree into two. The first tree has as uncoupled sectors the first M uncoupled sectors of the input tree f, whereas its coupled sector corresponds to the internal sector between uncoupled sectors M and M+1 of the original tree f. The second tree has as first uncoupled sector that same internal sector of f, followed by remaining N-M uncoupled sectors of f. It couples to the same sector as f. This operation is the inverse of insertat in the sense that if f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.merge","page":"Symmetry sectors and fusion trees","title":"TensorKit.merge","text":"merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = 1)\n-> <:AbstractDict{<:FusionTree{I, N₁+N₂}, <:Number}\n\nMerge two fusion trees together to a linear combination of fusion trees whose uncoupled sectors are those of f₁ followed by those of f₂, and where the two coupled sectors of f₁ and f₂ are further fused to c. In case of FusionStyle(I) == GenericFusion(), also a degeneracy label μ for the fusion of the coupled sectors of f₁ and f₂ to c needs to be specified.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.elementary_trace","page":"Symmetry sectors and fusion trees","title":"TensorKit.elementary_trace","text":"elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} -> <:AbstractDict{FusionTree{I,N-2}, <:Number}\n\nPerform an elementary trace of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.planar_trace-Union{Tuple{N₃}, Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N₃, Int64}, NTuple{N₃, Int64}}} where {I<:Sector, N, N₃}","page":"Symmetry sectors and fusion trees","title":"TensorKit.planar_trace","text":"planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}\n -> <:AbstractDict{FusionTree{I,N-2*N₃}, <:Number}\n\nPerform a planar trace of the uncoupled indices of the fusion tree f at q1 with those at q2, where q1[i] is connected to q2[i] for all i. The result is returned as a dictionary of output trees and corresponding coefficients.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.artin_braid","page":"Symmetry sectors and fusion trees","title":"TensorKit.artin_braid","text":"artin_braid(f::FusionTree, i; inv::Bool = false) -> <:AbstractDict{typeof(f), <:Number}\n\nPerform an elementary braid (Artin generator) of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.\n\nThe keyword inv determines whether index i will braid above or below index i+1, i.e. applying artin_braid(f′, i; inv = true) to all the outputs f′ of artin_braid(f, i; inv = false) and collecting the results should yield a single fusion tree with non-zero coefficient, namely f with coefficient 1. This keyword has no effect if BraidingStyle(sectortype(f)) isa SymmetricBraiding.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.braid-Union{Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N, Int64}, NTuple{N, Int64}}} where {I<:Sector, N}","page":"Symmetry sectors and fusion trees","title":"TensorKit.braid","text":"braid(f::FusionTree{<:Sector, N}, levels::NTuple{N, Int}, p::NTuple{N, Int})\n-> <:AbstractDict{typeof(t), <:Number}\n\nPerform a braiding of the uncoupled indices of the fusion tree f and return the result as a <:AbstractDict of output trees and corresponding coefficients. The braiding is determined by specifying that the new sector at position k corresponds to the sector that was originally at the position i = p[k], and assigning to every index i of the original fusion tree a distinct level or depth levels[i]. This permutation is then decomposed into elementary swaps between neighbouring indices, where the swaps are applied as braids such that if i and j cross, τ_ij is applied if levels[i] < levels[j] and τ_ji^-1 if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.permute-Union{Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N, Int64}}} where {I<:Sector, N}","page":"Symmetry sectors and fusion trees","title":"TensorKit.permute","text":"permute(f::FusionTree, p::NTuple{N, Int}) -> <:AbstractDict{typeof(t), <:Number}\n\nPerform a permutation of the uncoupled indices of the fusion tree f and returns the result as a <:AbstractDict of output trees and corresponding coefficients; this requires that BraidingStyle(sectortype(f)) isa SymmetricBraiding.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"These can be composed to implement elementary manipulations of fusion-splitting tree pairs, according to the following methods","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"# TODO: add documentation for the following methods\nTensorKit.bendright\nTensorKit.bendleft\nTensorKit.foldright\nTensorKit.foldleft\nTensorKit.cycleclockwise\nTensorKit.cycleanticlockwise","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Finally, these are used to define large manipulations of fusion-splitting tree pairs, which are then used in the index manipulation of AbstractTensorMap objects. The following methods defined on fusion splitting tree pairs have an associated definition for tensors.","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"repartition\ntranspose(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}\nbraid(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple, ::IndexTuple, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}\npermute(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}","category":"page"},{"location":"lib/sectors/#TensorKit.repartition","page":"Symmetry sectors and fusion trees","title":"TensorKit.repartition","text":"repartition(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, N::Int) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N}, FusionTree{I, N₁+N₂-N}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (f₁) and incoming sectors (f₂) respectively (with identical coupled sector f₁.coupled == f₂.coupled). Computes new trees and corresponding coefficients obtained from repartitioning the tree by bending incoming to outgoing sectors (or vice versa) in order to have N outgoing sectors.\n\n\n\n\n\nrepartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}\n -> tdst::AbstractTensorMap{S,N₁,N₂}\n\nReturn tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo repartition into an existing destination, see repartition!.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Base.transpose-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"Base.transpose","text":"transpose(f₁::FusionTree{I}, f₂::FusionTree{I},\n p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.braid-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N, Int64} where N, NTuple{N, Int64} where N, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"TensorKit.braid","text":"braid(f₁::FusionTree{I}, f₂::FusionTree{I},\n levels1::IndexTuple, levels2::IndexTuple,\n p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a fusion-splitting tree pair that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the splitting tree f₁ and fusion tree f₂, such that the incoming sectors f₂.uncoupled are fused to f₁.coupled == f₂.coupled and then to the outgoing sectors f₁.uncoupled. Compute new trees and corresponding coefficients obtained from repartitioning and braiding the tree such that sectors p1 become outgoing and sectors p2 become incoming. The uncoupled indices in splitting tree f₁ and fusion tree f₂ have levels (or depths) levels1 and levels2 respectively, which determines how indices braid. In particular, if i and j cross, τ_ij is applied if levels[i] < levels[j] and τ_ji^-1 if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"TensorKit.permute","text":"permute(f₁::FusionTree{I}, f₂::FusionTree{I},\n p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.\n\n\n\n\n\n","category":"method"},{"location":"#TensorKit.jl","page":"Home","title":"TensorKit.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"A Julia package for large-scale tensor computations, with a hint of category theory.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = TensorKit","category":"page"},{"location":"#Package-summary","page":"Home","title":"Package summary","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"TensorKit.jl aims to be a generic package for working with tensors as they appear throughout the physical sciences. TensorKit implements a parametric type Tensor (which is actually a specific case of the type TensorMap) and defines for these types a number of vector space operations (scalar multiplication, addition, norms and inner products), index operations (permutations) and linear algebra operations (multiplication, factorizations). Finally, tensor contractions can be performed using the @tensor macro from TensorOperations.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Currently, most effort is oriented towards tensors as they appear in the context of quantum many body physics and in particular the field of tensor networks. Such tensors often have large dimensions and take on a specific structure when symmetries are present. To deal with generic symmetries, we employ notations and concepts from category theory all the way down to the definition of a tensor.","category":"page"},{"location":"","page":"Home","title":"Home","text":"At the same time, TensorKit.jl focusses on computational efficiency and performance. The underlying storage of a tensor's data can be any DenseArray. Currently, certain operations are already multithreaded, either by distributing the different blocks in case of a structured tensor (i.e. with symmetries) or by using multithreading provided by the package Strided.jl. In the future, we also plan to investigate using CuArrays as underlying storage for the tensors data, so as to leverage GPUs for the different operations defined on tensors.","category":"page"},{"location":"#Contents-of-the-manual","page":"Home","title":"Contents of the manual","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"man/intro.md\", \"man/categories.md\", \"man/spaces.md\", \"man/sectors.md\", \"man/tensors.md\"]\nDepth = 3","category":"page"},{"location":"#Library-outline","page":"Home","title":"Library outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"lib/sectors.md\",\"lib/spaces.md\",\"lib/tensors.md\"]\nDepth = 2","category":"page"},{"location":"man/categories/#s_categories","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The purpose of this page (which can safely be skipped), is to explain how certain concepts and terminology from the theory of monoidal categories apply in the context of tensors. In particular, we are interested in the category mathbfVect, but our concept of tensors can be extended to morphisms of any category that shares similar properties. These properties are reviewed below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, we will as example also study the more general case of mathbfSVect, i.e. the category of super vector spaces, which contains mathbfVect as a subcategory and which are useful to describe fermions.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the end, the goal of identifying tensor manipulations in TensorKit.jl with concepts from category theory is to put the diagrammatic formulation of tensor networks in the most general context on a firmer footing. The following exposition is mostly based on [turaev], combined with input from [selinger], [kassel], [kitaev], and nLab, to which we refer for further information. Furthermore, we recommend the nice introduction of [beer].","category":"page"},{"location":"man/categories/#ss_categoryfunctor","page":"Optional introduction to category theory","title":"Categories, functors and natural transformations","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"To start, a category C consists of","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a class mathrmOb(C) of objects V, W, …\nfor each pair of objects V and W, a set mathrmHom_C(WV) of morphisms fWV; for a given map f, W is called the domain or source, and V the codomain or target.\ncomposition of morphisms fWV and gXW into (f g)XV that is associative, such that for hYX we have f (g h) = (f g) h\nfor each object V, an identity morphism mathrmid_VVV such that f mathrmid_W = f = mathrmid_V f.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The morphisms in mathrmHom_C(VV) are known as endomorphism and this set is also denoted as End_C(V). When the category C is clear, we can drop the subscript in mathrmHom(WV). A morphism fWV is an isomorphism if there exists a morphism f^-1VW called its inverse, such that f^-1 f = mathrmid_W and f f^-1 = mathrmid_V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Throughout this manual, we associate a graphical representation to morphisms and compositions thereof, which is sometimes referred to as the Penrose graphical calculus. To morphisms, we associate boxes with an incoming and outgoing line denoting the object in its source and target. The flow from source to target, and thus the direction of morphism composition f g (sometimes known as the flow of time) can be chosen left to right (like the arrow in fWV), right to left (like the composition order f g, or the matrix product), bottom to top (quantum field theory convention) or top to bottom (quantum circuit convention). Throughout this manual, we stick to this latter convention (which is not very common in manuscripts on category theory):","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: composition)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The direction of the arrows, which become important once we introduce duals, are also subject to convention, and are here chosen to follow the arrow in fWV, i.e. the source comes in and the target goes out. Strangely enough, this is opposite to the most common convention.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of interest, i.e. the category mathbf(Fin)Vect_𝕜 (or some subcategory thereof), the objects are (finite-dimensional) vector spaces over a field 𝕜, and the morphisms are linear maps between these vector spaces with \"matrix multiplication\" as composition. More importantly, the morphism spaces mathrmHom(WV) are themselves vector spaces. More general categories where the morphism spaces are vector spaces over a field 𝕜 (or modules over a ring 𝕜) and the composition of morphisms is a bilinear operation are called 𝕜-linear categories (or 𝕜-algebroids, or mathbfVect_𝕜-enriched categories). In that case, the endomorphisms mathrmEnd(V) are a 𝕜-algebra with mathrmid_V as the identity.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We also introduce some definitions which will be useful further on. A functor F between two categories C and D is, colloquially speaking, a mapping between categories that preserves morphism composition and identities. More specifically, FCD assigns to every object V mathrmOb(C) an object F(V) mathrmOb(D), and to each morphism f mathrmHom_C(WV) a morphism F(f) mathrmHom_D(F(W) F(V)) such that F(f) _D F(g) = F(f _C g) and F(mathrmid_V) = mathrmid_F(V) (where we denoted the possibly different composition laws in C and D explicitly with a subscript). In particular, every category C has an identity functor 1_C that acts trivially on objects and morphisms. Functors can also be composed. A 𝕜-linear functor between two 𝕜-linear categories has a linear action on morphisms.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Given two categories C and D, and two functors F and G that map from C to D, a natural transformation φFG is a family of morphisms φ_V mathrmHom_D(F(V)G(V)) in D, labeled by the objects V of C, such that φ_V F(f) = G(f) φ_W for all morphisms f mathrmHom_C(WV). If all morphisms φ_V are isomorphisms, φ is called a natural isomorphism and the two functors F and G are said to be isomorphic.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The product of two categories C and C, denoted C C, is the category with objects mathrmOb(CC) = mathrmOb(C) mathrmOb(C), whose elements are denoted as tuples (VV), and morphisms mathrmHom_CC((WW) (VV)) = mathrmHom_C(WV) mathrmHom_C(WV). Composition acts as (ff) (gg) = (ff gg) and the identity is given by mathrmid_VV = (mathrmid_V mathrmid_V). In a similar fashion, we can define the product of functors FCD and FCD as a functor FF (CC)(DD) mapping objects (VV) to (F(V) F(V)) and morphisms (ff) to (F(f) F(f)).","category":"page"},{"location":"man/categories/#ss_monoidalcategory","page":"Optional introduction to category theory","title":"Monoidal categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The next property of the category mathbfVect that we want to highlight and generalize is that which allows to take tensor products. Indeed, a category C is said to be a tensor category (a.k.a. a monoidal category), if it has","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a binary operation on objects mathrmOb(C) mathrmOb(C) mathrmOb(C)\na binary operation on morphisms, also denoted as , such that mathrmHom_C(W_1V_1) mathrmHom_C(W_2V_2) mathrmHom_C(W_1 W_2 V_1 V_2)\nan identity or unit object I\nthree families of natural isomorphisms:\n V mathrmOb(C), a left unitor (a.k.a. left unitality constraint) λ_V I V V\n V mathrmOb(C), a right unitor (a.k.a. right unitality constraint) ρ_V V I V\n V_1 V_2 V_3 mathrmOb(C), an associator (a.k.a. associativity constraint) α_V_1V_2V_3(V_1 V_2) V_3 V_1 (V_2 V_3)\nthat satisfy certain consistency conditions (coherence axioms), which are known as the pentagon equation (stating that the two possible mappings from (((V_1 V_2) V_3) V_4) to (V_1 (V_2 (V_3 V_4))) are compatible) and the triangle equation (expressing compatibility between the two possible ways to map ((V_1 I) V_2) to (V_1 (I V_2))).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In terms of functors and natural transformations, is a functor from the product category C C to C. Furthermore, the left (or right) unitor λ (or ρ) is a natural isomorphism between a nameless functor CC that maps objects V I V (or VV I) and the identity functor 1_C. Similarly, the associator α is a natural isomorphism between the two functors ( 1_C) and (1_C ) from C C C to C. In a k-linear category, the tensor product of morphisms is also a bilinear operation. A monoidal category is said to be strict if I V = V = V I and (V_1V_2)V_3 = V_1(V_2V_3), and the left and right unitor and associator are just the identity morphisms for these objects.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For the category mathbfVect, the identity object I is just the scalar field 𝕜 over which the vector spaces are defined, and which can be identified with a one- dimensional vector space. This is not automatically a strict category, especially if one considers how to represent tensor maps on a computer. The distinction between V, I V and V I amounts to adding or removing an extra factor I to the tensor product structure of the domain or codomain, and so the left and right unitor are analogous to removing extra dimensions of size 1 from a multidimensional array. The fact that arrays with and without additional dimensions 1 are not automatically identical and an actual operation is required to insert or remove them, has led to some discussion in several programming languages that provide native support for multidimensional arrays.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For what concerns the associator, the distinction between (V_1 V_2) V_3 and V_1 (V_2 V_3) is typically absent for simple tensors or multidimensional arrays. However, this grouping can be taken to indicate how to build the fusion tree for coupling irreps to a joint irrep in the case of symmetric tensors. As such, going from one to the other requires a recoupling (F-move) which has a non-trivial action on the reduced blocks. We elaborate on this in the context of Fusion categories below. However, we can already note that we will always represent tensor products using a canonical order (((V_1 V_2) V_3) V_N). A similar approach can be followed to turn any tensor category into a strict tensor category (see Section XI.5 of [kassel]).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The different natural isomorphisms involving the unit object have various relations, such as λ_VW α_IVW = λ_V mathrmid_W and λ_I = ρ_I I I I. The last relation defines an isomorphism between I I and I, which can also be used to state that for f g End_C(I), f g = ρ_I (f g) λ_I^-1 = g f. Hence, the tensor product of morphisms in End_C(I) can be related to morphism composition in End_C(I), and furthermore, the monoid of endomorphisms End_C(I) is commutative (abelian). In the case of a 𝕜-linear category, it is an abelian 𝕜-algebra. In the case of mathbfVect, mathrmEnd(I) is indeed isomorphic to the field of scalars 𝕜. We return to the general case where End_C(I) is isomorphic to 𝕜 itself in the section on pre-fusion categories.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Furthermore, Mac Lane's coherence theorem states that the triangle and pentagon condition are sufficient to ensure that any consistent diagram made of associators and left and right unitors (involving all possible objects in C) commutes. For what concerns the graphical notation, the natural isomorphisms will not be represented and we make no distinction between (V_1 V_2) V_3 and V_1 (V_2 V_3). Similarly, the identity object I can be added or removed at will, and when drawn, is often represented by a dotted or dashed line. Note that any consistent way of inserting the associator or left or right unitor to convert a graphical representation to a diagram of compositions and tensor products of morphisms gives rise to the same result, by virtue of Mac Lane's coherence theorem. Using the horizontal direction (left to right) to stack tensor products, this gives rise to the following graphical notation for the tensor product of two morphisms, and for a general morphism t between a tensor product of objects in source and target:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: tensorproduct)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Another relevant example is the category mathbfSVect_𝕜, which has as objects super vector spaces over 𝕜, which are vector spaces with a ℤ₂ grading, i.e. they are decomposed as a direct sum V = V_0 V_1. Furthermore, the morphisms between two super vector spaces are restricted to be grading preserving, i.e. f mathrmHom_mathbfSVect(WV) has f(W_0) V_0 and f(W_1) V_1. The graded tensor product between two super vector spaces is defined as (V_mathrmgW) = (V _mathrmg W)_0 (V _mathrmg W)_1 with (V _mathrmg W)_0 = (V_0 W_0) (V_1 W_1) and (V _mathrmg W)_1 = (V_0 W_1) (V_1 W_0). The unit object I is again isomorphic to 𝕜, i.e. I_0 = 𝕜 and I_1 = 0, a zero-dimensional vector space. In particular, the category mathbfSVect_𝕜 contains mathbfVect_𝕜 as a (monoidal) subcategory, by only selecting those objects V for which V_1 = 0. We will return to the example of mathbfSVect throughout the remainder of this page.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, we generalize the notion of a functor between monoidal categories. A monoidal functor between two tensor categories (C _C I_C α_C λ_C ρ_C) and (D _D I_D α_D λ_D ρ_D) is a functor FCD together with two monoidal constraints, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a morphism F₀I_D F(I_C);\na natural transformation F_2=F_2(XY) F(X) _D F(Y) F(X _C Y) XY mathrmOb(C) between the functors _D(FF) and F _C from CC to D.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A monoidal natural transformation φ between two monoidal functors FCD and GCDis a natural transformation φFG that furthermore satisfies","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"φ_I_C F_0 = G_0;\n XY mathrmOb(C): φ_X Y F_2(XY) = G_2(XY)(φ_X φ_Y).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For further reference, we also define the following categories which can be associated with the category mathcalC = (C I α λ ρ)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathcalC^mathrmop = (C^mathrmop I α^mathrmop λ^mathrmop ρ^mathrmop) where the opposite category C^mathrmop has the same objects as C but has mathrmHom_C^mathrmop(XY) = mathrmHom_C(YX) and a composition law g ^mathrmop f = f g, with the composition law of C. Furthermore, we have α^mathrmop_XYZ = (α_XYZ)^-1, λ^mathrmop_X = (λ_X)^-1 and ρ^mathrmop_X = (ρ_X)^-1;\nmathcalC^mathrmop = (C ^mathrmop I α^mathrmop λ^mathrmop ρ^mathrmop) where the functor ^mathrmopCC C is the opposite monoidal product, which acts as X ^mathrmop Y = Y X on objects and similar on morphisms. Furthermore, α^mathrmop_XYZ = (α_ZYX)^-1, λ^mathrmop_X = ρ_X and ρ^mathrmop_X = λ_X;\nThe two previous transformations (which commute) composed: mathcalC^mathrmrev = (C^mathrmop ^mathrmop I α^mathrmrev λ^mathrmrev ρ^mathrmrev) with α^mathrmrev_XYZ = α_ZYX, λ^mathrmrev_X = (ρ_X)^-1, ρ^mathrmrev_X = (λ_X)^-1.","category":"page"},{"location":"man/categories/#ss_dual","page":"Optional introduction to category theory","title":"Duality: rigid, pivotal and spherical categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Another property of the category mathbfVect that we want to generalize is the notion of duals. For a vector space V, i.e. an object of mathbfVect, the dual V^* is itself a vector space. Evaluating the action of dual vector on a vector can, because of linearity, be interpreted as a morphism from V^* V to I. Note that elements of a vector space V have no categorical counterpart in themselves, but can be interpreted as morphism from I to V. To map morphisms from mathrmHom(WV) to elements of V W^*, i.e. morphisms in mathrmHom(I V W^*), we use another morphism mathrmHom(I W W^*) which can be considered as the inverse of the evaluation map.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Hence, duality in a monoidal category is defined via an exact paring, i.e. two families of non-degenerate morphisms, the evaluation (or co-unit) ϵ_V ^V V I and the coevaluation (or unit) η_V I V ^V which satisfy the \"snake rules\":","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"ρ_V (mathrmid_V ϵ_V) (η_V mathrmid_V) λ_V^-1 = mathrmid_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"λ_^V^-1 (ϵ_V mathrmid_^V) (mathrmid_^V η_V) ρ_^V^-1 = mathrmid_^V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and can be used to define an isomorphism between mathrmHom(W V U) and mathrmHom(W U ^V) for any triple of objects U V W mathrmOb(C). Note that if there are different duals (with corresponding exact pairings) associated to an object V, a mixed snake composition using the evaluation of one and coevaluation of the other duality can be used to construct an isomorphism between the two associated dual objects. Hence, duality is unique up to isomorphisms.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For (real or complex) vector spaces, we denote the dual as V^*, a notation that we preserve for pivotal categories (see below). Using a bra-ket notation and a generic basis n for V and dual basis m for V^* (such that mn = δ_mn), the evaluation is given by ϵ_V^V V ℂ m n δ_mn and the coevaluation or unit is η_Vℂ V ^Vα α _n n n. Note that this does not require an inner product, i.e. no relation or mapping from n to n was defined. For a general tensor map tW_1 W_2 W_N_2 V_1 V_2 V_N_1, by successively applying η_W_N_2, η_W_N_2-1, …, η_W_1 (in combination with the left or right unitor), we obtain a tensor in V_1 V_2 V_N_1 W_N_2^* W_1^*. Hence, we can define or identify (W_1 W_2 W_N_2)^* = W_N_2^* W_1^*. Indeed, it can be shown that for any category which has duals for objects V and W, an exact pairing between V W and ^W ^V can be constructed out of the evaluation and coevaluation of V and W, such that ^W ^V is at least isomorphic to ^(V W).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Graphically, we represent the exact pairing and snake rules as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: left dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that we denote the dual objects ^V as a line V with arrows pointing in the opposite (i.e. upward) direction. This notation is related to quantum field theory, where anti-particles are (to some extent) interpreted as particles running backwards in time.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"These exact pairings are known as the left evaluation and coevaluation, and ^V is the left dual of V. Likewise, we can also define a right dual V^ of V and associated pairings, the right evaluation tildeϵ_V V V^ I and coevaluation tildeη_V I V^ V, satisfying","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: right dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, one could choose tildeϵ_^V = ϵ_V and thus define V as the right dual of ^V. While there might be other choices, this choice must at least be isomorphic, such that (^V)^ V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If objects V and W have left (respectively right) duals, than for a morphism f mathrmHom(WV), we furthermore define the left (respectively right) transpose ^f mathrmHom(^V ^W) (respectively f^ mathrmHom(V^ W^)) as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: transpose)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where on the right we also illustrate the mapping from t mathrmHom(W_1 W_2 W_3 V_1 V_2) to a morphism in mathrmHom(I V_1 V_2 ^ W_3 ^ W_2 ^ W_1).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the graphical notation, at least the lines with opposite arrows, do not allow to distinguish between the right dual V^ and the left dual ^V. We come back to this point below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A left (or right) duality in a (monoidal) category is now defined as an association of a left (or right) dual with every object of the category, with corresponding exact pairings, and a category admitting such a duality is a left (or right) rigid category (or left or right autonomous category). Given that left (or right) morphism transposition satisfies ^(f g)= ^g ^f= ^f ^mathrmop ^g and recalling ^(V W) = ^W ^V (and similar for right duality), we can define duality in a functorial way. A (left or right) rigid category mathcalC is a category which admits a (left or right) duality functor, i.e. a functor from mathcalC to mathcalC^mathrmrev that maps objects to its (left or right) dual, and morphisms to its (left or right) transpose. In particular, the snake rules can now be read as the functioral requirement that ^(mathrmid_V) = mathrmid_^V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In all of this, left and right duality can be completely distinct. Equivalently, the left dual of the left dual of an object V, i.e. ^V is not necessarily V itself, nor do the exact pairings enable us to construct an isomorphism between ^V and V. For finite-dimensional vector spaces, however, ^V and V, or thus ^V and V^ are known to be isomorphic. The categorical generalization is that of a pivotal category (or sovereign category), i.e. a monoidal category with two-sided duals X^* = ^X = X^ = X^* such that the left and right duality functor coincide, and thus also the left and right transpose of morphisms, i.e. f^* = ^f = f^ mathrmHom(V^*W^*) for any fmathrmHom(WV). Given that tildeϵ_X and tildeη_X can be interpreted as an exact pairing ϵ_X^* and η_X^*, this can be used to recognize X as a left dual of X^*, which is then not necessarily equal but at least isomorphic to X^** with the isomorphism given by the mixed snake composition alluded to in the beginning of this section, i.e. δ_X X X^** given by δ_X = (tildeϵ_X mathrmid_X^*) (mathrmid_X η_X^*). A more formal statement is that δ is a natural isomorphism between the double dual functor and the identity functor of a category C. In a similar manner, such a δ can be used to define a natural isomorphism between left and right dual functor (which is a slight generalization of the above definition of a pivotal category), and as such it is often called the pivotal structure.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Hence, in a pivotal category, left and right duals are the same or isomorphic, and so are objects and their double duals. As such, we will not distinguish between them in the graphical representation and suppress the natural isomorphism δ. Note, as already suggested by the graphical notation above, that we can interpret transposing a morphism as rotating its graphical notation by 180 degrees (either way).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Furthermore, in a pivotal category, we can define a map from mathrmEnd(V), the endomorphisms of an object V to endomorphisms of the identity object I, i.e. the field of scalars in the case of the category mathbfVect, known as the trace of f. In fact, we can define both a left trace as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr_mathrml(f) = ϵ_V (mathrmid_V^* f) tildeη_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and a right trace as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr_mathrmr(f) = tildeϵ_V (f mathrmid_V^*) η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"They are graphically represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: trace)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and they do not need to coincide. Note that mathrmtr_mathrml(f) = mathrmtr_mathrmr(f*) and that mathrmtr_mathrmlmathrmr(fg) = mathrmtr_mathrmlmathrmr(gf). The (left or right) trace of the identity morphism mathrmid_V defines the corresponding (left or right) dimension of the object V, i.e. mathrmdim_mathrmlmathrmr(V) = tr_mathrmlmathrmr(mathrmid_V). In a spherical category, both definitions of the trace coincide for all V and we simply refer to the trace mathrmtr(f) of an endomorphism. The particular value mathrmdim(V) = mathrmtr(mathrmid_V) is known as the (quantum) dimension of the object V, referred to as dim(V) in TensorKit.jl.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For further information and a more detailed treatment of rigid and pivotal categories, we refer to [turaev] and [selinger]. We conclude this section by studying the example of mathbfSVect. Let us, in every super vector space V, define a basis n that is compatible with the grading, such n=01 indicates that n V_n. We again define a dual basis m for V^* (such that mn = δ_mn), and then define the left evaluation by ϵ_VV^* V ℂ m _mathrmg n mn = δ_mn and the left coevaluation by η_Vℂ V V^*α α _n n _mathrmg n. Note that this does not require an inner product and satisfies the snake rules. For the right evaluation and coevaluation, there are two natural choices, namely tildeϵ_VV V^* ℂ n _mathrmg m (1)^n δ_mn and tildeη_Vℂ V^* V α _n (1)^n n _mathrmg n. The resulting trace of an endomorphism f mathrmEnd(V) is given by mathrmtr^mathrml(f) = mathrmtr^mathrmr(f) = mathrmtr(f) = _n ( 1)^n nfn and is known as either the regular trace (in the case of +1) or the supertrace (in the case of -1). In particular, mathrmdim(V) = mathrmdim(V_0) mathrmdim(V_1), and can be negative in the case of the supertrace. Both are valid choices to make mathbfSVect into a spherical category.","category":"page"},{"location":"man/categories/#ss_braiding","page":"Optional introduction to category theory","title":"Braidings, twists and ribbons","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"While duality and the pivotal structure allow to move vector spaces back and forth between the domain (source) and codomain (target) of a tensor map, reordering vector spaces within the domain or codomain of a tensor map , i.e. within a tensor product V_1 V_2 V_N requires additional structure. In particular, we need at the very least a braided tensor category C, which is endowed with a braiding τ, i.e. a natural isomorphism τ_VWVW WV_VW mathrmOb(C) between the functors and ^mathrmop such that τ_VV(f g) = (g f)τ_WW for any morphisms f mathrmHom(WV) and g mathrmHom(WV). A valid braiding needs to satisfy a coherence condition with the associator α known as the hexagon equation, which expresses that the braiding is -multiplicative, i.e. τ_UVW = (mathrmid_V τ_UW)(τ_UVmathrmid_W) and τ_UVW = (τ_UWmathrmid_VW)(mathrmid_U τ_VW) (where the associator has been omitted). We also have λ_V τ_VI = ρ_VI, ρ_V τ_IV = λ_V and τ_VI = τ_IV^-1 for any V mathrmOb(C).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The braiding isomorphism τ_VW and its inverse are graphically represented as the lines V and W crossing over and under each other:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"such that we have","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding relations)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where the expression on the right hand side, τ_WVτ_VW can generically not be simplified. Hence, for general braidings, there is no unique choice to identify a tensor in VW and WV, as the isomorphisms τ_VW, τ_WV^-1, τ_VW τ_WV τ_VW, … mapping from VW to WV can all be different. In order for there to be a unique map from V_1 V_2 V_N to any permutation of the objects in this tensor product, the braiding needs to be symmetric, i.e. τ_VW = τ_WV^-1 or, equivalently τ_WV τ_VW = mathrmid_VW. The resulting category is then referred to as a symmetric tensor category. In a graphical representation, it means that there is no distinction between over- and under- crossings and, as such, lines can just cross, where the crossing represents the action of τ_VW = τ_WV^-1.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of the category mathbfVect a valid braiding consists of just flipping the the objects/morphisms involved, e.g. for a simple cartesian tensor, permuting the tensor indices is equivalent to applying Julia's function permutedims on the underlying data. Less trivial braiding implementations arise in the context of tensors with symmetries (where the fusion tree needs to be reordered, as discussed in Sectors, representation spaces and fusion trees) or in the case of mathbfSVect, which will again be studied in detail at the end of this section.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The braiding of a space and a dual space also follows naturally, it is given by τ_V^*W = λ_W V^* (ϵ_V mathrmid_W V^*) (mathrmid_V^* τ_VW^-1 mathrmid_V^*) (mathrmid_V^* W η_V) ρ_V^* W^-1, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Balanced categories C are braided categories that come with a twist θ, a natural transformation from the identity functor 1_C to itself, such that θ_V f = f θ_W for all morphisms f mathrmHom(WV), and for which main requirement is that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_VW = τ_WV (θ_W θ_V) τ_VW = (θ_V θ_W) τ_WV τ_VW","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, a braided pivotal category is balanced, as we can even define two such twists, namely a left and right twist given by","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ^mathrml_V = (ϵ_V mathrmid_V)(mathrmid_V* τ_VV) (tildeη_V mathrmid_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ^mathrmr_V = (mathrmid_V tildeϵ_V)(τ_VV mathrmid_V*)(mathrmid_V ϵ_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where we omitted the necessary left and right unitors and associators. Graphically, the twists and their inverse (for which we refer to [turaev]) are then represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: twists)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The graphical representation also makes it straightforward to verify that (θ^mathrml_V)^* = θ^mathrmr_V^*, (θ^mathrmr_V)^* = θ^mathrml_V^* and mathrmtr_mathrml( θ^mathrmr_V ) = mathrmtr_mathrmr( θ^mathrml_V ).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"When θ^mathrml = θ^mathrmr, or thus, equivalently, θ_V^* = θ_V^* for either θ^mathrml or θ^mathrmr, the category is said to be tortile or also a ribbon category, because its graphical representation is compatible with the isotopy of a ribbon, i.e. where the lines representing objects are depicted as ribbons. For convenience, we continue to denote them as lines. Ribbon categories are necessarily spherical, i.e. one can prove the equivalence of the left and right trace.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Alternatively, one can start from a balanced and rigid category (e.g. with a left duality), and use the twist θ, which should satisfy θ_V^* = θ_V^*, to define a pivotal structure, or, to define the exact pairing for the right dual functor as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeη_V = τ_VV^* (θ_V mathrmid_V^*) η_V = (mathrmid_V^* θ_V) τ_VV^* η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeϵ_V = ϵ_V (mathrmid_V^* θ_V) τ_VV^* = ϵ_V τ_VV^* (θ_V mathrmid_V^*)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: pivotal from twist)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where we have drawn θ as θ^mathrml on the left and as θ^mathrmr on the right, but in this case the starting assumption was that they are one and the same, and we defined the pivotal structure so as to make it compatible with the graphical representation. This construction of the pivotal structure can than be used to define the trace, which is spherical, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr(f) = ϵ_V τ_VV^* (( θ_V f) mathrmid_V^*) η_V = ϵ_V (mathrmid_V^* (f θ_V)) τ_VV^* η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note finally, that a ribbon category where the braiding is symmetric, is known as a compact closed category. For a symmetric braiding, the trivial twist θ_V = mathrmid_V is always a valid choice, but it might not be the choice that one necessarily want to use. Let us study the case of mathbfSVect again. Reinvoking our basis m V and n W, the braiding τ_VW is given by the Koszul sign rule, i.e. τ_VWm _mathrmg n (-1)^m n n _mathrmg m. Hence, braiding amounts to flipping the two spaces, but picks up an additional minus sign if both m V_1 and n W_1. This braiding is symmetric, i.e. τ_WV τ_VW = mathrmid_VW. Between spaces and dual spaces, we similarly obtain the braiding rule m _mathrmg n (-1)^m n n _mathrmg m. Combining the braiding and the pivotal structure gives rise to a ribbon category, and thus, a compact closed category, where the resulting twist is given by θ_V n (1)^n n for tildeϵ_VV V^* ℂ n _mathrmg m (1)^n δ_mn and corresponding tildeη_V. Hence, if the right (co)evaluation contains a minus sign, the twist is θ_V = mathrmid_V, which, as mentioned above, is always a valid twist for a symmetric category. However, if the right (co)evaluation contains no minus sign, the twist acts as the parity endomorphism, i.e. as +1 on V_0 and as -1 on V_1, which, as we will see in the next section, corresponds to a choice bearing additional structure.","category":"page"},{"location":"man/categories/#ss_adjoints","page":"Optional introduction to category theory","title":"Adjoints and dagger categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A final aspect of categories as they are relevant to physics, and in particular quantum physics, is the notion of an adjoint or dagger. A dagger category C is a category together with an involutive functor CC^mathrmop, i.e. it acts as the identity on objects, whereas on morphisms fWV it defines a morphism f^VW such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmid_V^ = mathrmid_V\n(f g)^ = f^ ^mathrmop g^ = g^ f^\n(f^)^ = f","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Sometimes also the symbol * is used instead of , however we have already used * to denote dual objects and transposed morphisms in the case of a pivotal category.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If the category is ℂ-linear, the dagger functor is often assumed to be antilinear, i.e. (λ f)^ = barλ f^ for λ ℂ and f mathrmHom(VW). In a dagger category, a morphism fWV is said to be unitary if it is an isomorphism and f^-1 = f^. Furthermore, an endomorphism fVV is hermitian or self-adjoint if f^ = f. Finally, we will also use the term isometry for a morphism fWV which has a left inverse f^, i.e. such that f^ f = mathrmid_W, but for which f f^ is not necessarily the identity (but rather some orthogonal projector, i.e. a hermitian idempotent in mathrmEnd(V)).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the graphical representation, the dagger of a morphism can be represented by mirroring the morphism around a horizontal axis, and then reversing all arrows (bringing them back to their original orientation before the mirror operation):","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: dagger)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where for completeness we have also depicted the graphical representation of the transpose, which is a very different operation. In particular, the dagger does not reverse the order of the tensor product. Note that, for readibility, we have not mirrored or rotated the label in the box, but this implies that we need to use a type of box for which the action of mirroring or rotating can be observed.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A dagger monoidal category is one in which the associator and left and right unitor are unitary morphisms. Similarly, a dagger braided category also has a unitary braiding, and a dagger balanced category in addition has a unitary twist.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"There is more to be said about the interplay between the dagger and duals. Given a left evaluation ϵ_V V^* V I and coevaluation η_V I V V^*, we can define a right evaluation tildeϵ_V = (η_V)^ and coevaluation tildeη_V = (ϵ_V)^. Hence, left rigid dagger categories are automatically pivotal dagger categories.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The (right) twist defined via the pivotal structure now becomes","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_V = (mathrmid_V (η_V)^) (τ_VV mathrmid_V^*) (mathrmid_V η_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and is itself unitary. Even for a symmetric category, the twist defined as such must not be the identity, as we discuss for the mathbfSVect example below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, the dagger allows to define two Hermitian forms on the morphisms, namely f g _mathrmlmathrmr = mathrmtr_mathrmlmathrmr(f^ g), which coincide for a spherical category. For a unitary 𝕜-linear category, these Hermitian forms should be positive definite and thus define an inner product on each of the homomorphism spaces mathrmHom(WV). In particular then, dimensions of objects are positive, as they satisfy mathrmdim_mathrmlmathrmr(V) = mathrmid_V mathrmid_V _mathrmlmathrmr.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"This concludes the most important categorical definitions and structures that we want to discuss for the category mathbfVect, but which can also be realized in other categories. In particular, the interface of TensorKit.jl could in principle represent morphisms from any 𝕜-linear monoidal category, but assumes categories with duals to be pivotal and in fact spherical, and categories with a braiding to be ribbon categories. A dagger ribbon category where the braiding is symmetric, i.e. a dagger category which is also a compact closed category and where the right (co)evaluation is given via the dagger of the left (co)evaluation is called a dagger compact category. This is the playground of quantum mechanics of bosonic and fermionic systems. However, we also allow for non- symmetric braiding in TensorKit.jl, though this functionality is currently much more limited.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Again studying the category mathbfSVect_ℂ (now explicitly over the complex numbers) and using the conventional adjoint or the complex Euclidean inner product to define the dagger functor, the right (co)evaluation that is obtained from applying the dagger to the left (co)evaluation is the definition we gave above with the +1 sign. This choice gives rise to a regular trace (versus the supertrace) of endomorphisms, to positive dimensions, and a non-trivial twist that acts as the parity endomorphism. The resulting category is then a dagger compact category, that can be used for the quantum mechanical description of fermionic systems. The bosonic version is obtained by restricting to the subcategory mathbfVect.","category":"page"},{"location":"man/categories/#ss_fusion","page":"Optional introduction to category theory","title":"Direct sums, simple objects and fusion categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"These last two section on fusion categories is also applicable, in a straightforward manner, to mathbfVect and mathbfSVect, but is rather meant to provide the background of working with symmetries. We first need two new concepts:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"An object W mathrmOb(C) is a direct sum of objects V_1 V_2 V_k mathrmOb(C) if there exists a family morphisms x_α mathrmHom(V_αW) and y^α mathrmHom(WV_α) such that mathrmid_W = _α=1^k x_α y^α and y^α x_β = δ^α_β mathrmid_V_α. The morphisms x_α and y^α are known as inclusions and projections respectively, and in the context of dagger categories it is natural to assume y^α = x_α^ in order to obtain an orthogonal direct sum decomposition.\nA simple object V mathrmOb(C) of a 𝕜-linear category C is an object for which End_C(V) 𝕜, i.e. the algebra of endomorphisms on V is isomorphic to the field (or ring) 𝕜. As End_C(V) always contains the identity morphism mathrmid_V, and this must be the only linearly independent endomorphism if V is a simple object, the isomorphism between mathrmEnd_C(V) and 𝕜 is typically of the form k 𝕜 k mathrmid_V End_C(V). In particular, for mathbfSVect and its subcategory mathbfVect, the unit object I is a simple object.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, for a pivotal 𝕜-linear category where I is simple, it holds that the left and right dimensions of any simple object V are invertible in 𝕜, and that any endomorphism f mathrmEnd(V) can be written as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"f = (mathrmdim_mathrml(V))^-1 mathrmtr_mathrml(f) mathrmid_V = (mathrmdim_mathrmr(V))^-1 mathrmtr_mathrmr(f) mathrmid_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Strictly speaking, this holds only if the category is non-degenerate, which means that I is simple and that any non-degenerate pairing eV W I induces a non- degenerate pairing mathrmHom(IV) mathrmHom(IW) mathrmEnd(I). This property is always satisfied for a pre-fusion category C, i.e. a monoidal 𝕜- linear category having a set mathcalS mathrmOb(C) of simple objects mathcalS=I V_1 V_2 ldots such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"the monoidal unit I_C mathcalS;\nmathrmHom_C(V_iV_j) = 0 (the singleton set containing only the zero homomorphism) for any distinct V_i V_j mathcalS;\nevery object V mathrmOb(C) can be written as a direct sum of a finite family of elements from mathcalS.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that in the direct sum decomposition of an object V, a particular simple object V_i might appear multiple times. This number is known as the multiplicity index N^V_i, and equal to the rank of mathrmHom(VV_i) or, equivalently, of mathrmHom(V_iV). Hence, we can choose inclusion and projection maps x_iμV_iV and y^iμVV_i for μ = 1ldots N^V_i, such that mathrmid_V = sum_isum_μ=1^N_V^i x_iμ y^iμ and y^iμ x_jν = δ^i_j δ^μ_ν. In particular, for a simple object V, it either appears in mathcalS or is isomorphic to an object S. We thus have N^V_i = 1 for one particular object V_i and N^V_j= 0 for all other j, with x_i and y^i = (x_i)^-1 representing the isomorphism between V and V_i.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The homomorphisms between two general objects W and V in a pre-fusion category can be decomposed as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmHom(WV) _V_i mathcalS mathrmHom(WV_i) mathrmHom(V_iV)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and thus that the rank of mathrmHom(WV) is given by _i N^W_i N^V_i.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A fusion category is a pre-fusion category that has (left or right) duals, i.e. that is rigid, and that only has a finite number of isomorphism classes of simple objects. Note that the duality functor maps mathrmEnd(V) to mathrmEnd(V^*), such that, if V is a simple object, so must be V^*. Henceforth, we will be sloppy about the distinction between a pre-fusion or fusion category, only use the latter term, even when it is not fully justified.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Before continuing, let us use some examples to sketch the relevance of the concept of fusion categories. As mentioned, the categories mathbfVect_𝕜 and mathbfSVect_𝕜 have I 𝕜 as simple object. For mathbfVect, this is the only simple object, i.e. any other vector space V over 𝕜, can be thought of as a direct sum over N^V_I = mathrmdim(V) multiple copies of 𝕜. In mathbfSVect, the object J = 0 𝕜 with J_0=0 the zero dimensional space and J_1 𝕜 is another simple object. Clearly, there are no non-zero grading preserving morphisms between I and J, i.e. mathrmHom(IJ) = 0, whereas mathrmHom(JJ) 𝕜. Any other super vector space V=V_0 V_1 can be written as a direct sum over N^V_I = mathrmdim(V_0) copies of I and N^V_J = mathrmdim(V_1) copies of J.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A more representative example is that of the category C = mathbfRep_mathsfG, the category of representations of a group mathsfG. Colloquially, this could be thought of as a subcategory of mathbfVect containing as objects vector spaces V on which a representation of mathsfG is defined, denoted as u_V(g) for g mathsfG, and as morphisms the equivariant transformations, i.e. intertwiners between the representations on the source and target:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmHom_C(WV) = f mathrmHom_mathbfVect(WV) u_V(g) f = f u_W(g) g G","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the u_V(g) is itself generally not an element from End_C(V). Simple objects V_a are those corresponding irreducible representations (irreps) a of the group mathsfG, for which Schur's lemma implies End_C(V_a) 𝕜 and mathrmHom_C(V_a V_b) = 0 if a and b are not equivalent irreps. On the dual space V^*, the group acts with the contragradient representation, i.e. u_V^*(g) = ((u_V(g))^-1)^* = u_V(g^-1)^*, where one should remind that ^* denotes the transpose. For a finite group or compact Lie group, we can introduce a dagger and restrict to unitary representations, such that u_V(g)^-1 = u_V(g)^ and the contragradient representation becomes the complex conjugated representation, denoted as u_V^*(g) = baru_V(g). The resulting category can then be given the structure of a unitary ribbon (pre-)fusion category. (Note that the number of isomorphism classes of simple objects, i.e. the number of non-equivalent irreps, is finite only in the case of a finite group). This example is very relevant to working with symmetries in TensorKit.jl, and will be expanded upon in more detail below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Fusion categories have a number of simplifying properties. A pivotal fusion category is spherical as soon as mathrmdim_mathrml(V_i) = mathrmdim_mathrmr(V_i) (i.e. the trace of the identity morphism) for all (isomorphism classes of) simple objects (note that all isomorphic simple objects have the same dimension). A braided pivotal fusion category is spherical if and only if it is a ribbon category.","category":"page"},{"location":"man/categories/#ss_topologicalfusion","page":"Optional introduction to category theory","title":"Topological data of a unitary pivotal fusion category","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"More explicitly, the different structures (monoidal structure, duals and pivotal structure, braiding and twists) in a fusion category can be characterized in terms of the simple objects, which we will henceforth denoted with just a instead of V_a. This gives rise to what is known as the topological data of a unitary pivotal fusion category, most importantly the N, F and R symbols, which are introduced in this final section.","category":"page"},{"location":"man/categories/#Monoidal-structure","page":"Optional introduction to category theory","title":"Monoidal structure","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Starting with the monoidal or tensor product, we start by characterizing how the object a b can be decomposed as a direct sum over simple objects c, which gives rise to the multiplicity indices N_c^ab, as well as the inclusion maps, which we henceforth denote as X_cμ^abcab for μ=1N^c_ab. In the context of a unitary fusion category, on which we now focus, the corresponding projection maps are Y^cμ_ab = (X_cμ^ab)^abc such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(X_cμ^ab)^ X_cμ^ab = δ_cc δ_μμ mathrmid_c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Graphically, we represent these relations as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: fusion)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and also refer to the inclusion and projection maps as splitting and fusion tensor, respectively.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For both (ab)c and a(bc), which are isomorphic via the associator α_abc, we must thus obtain a direct sum decomposition with the same multiplicity indices, leading to the associativity constraint","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"N_d^abc= _e N_e^ab N_d^ec = _f N_f^bc N_d^af","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The corresponding inclusion maps can be chosen as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"X_d(eμν)^abc = (X_eμ^ab mathrmid_c) X_dν^ec d(ab)c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeX_d(fκλ)^abc = (mathrmid_a X_fκ^bc) X_dλ^af da(bc)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and satisfy","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(X_d(eμν)^abc)^ X_d(eμν)^abc = δ_ee δ_μμ δ_νν δ_dd mathrmid_d","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"_deμν X_d(eμν)^abc (X_d(eμν)^abc)^ = mathrmid_(ab)c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and similar for tildeX_d(fκλ)^abc. Applying the associator leads to a relation","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"α_abc X_d(eμν)^abc = _fκλ F^abc_d_(eμν)^(fκλ) tildeX_d(fκλ)^abc","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"which defines the F-symbol, i.e. the matrix elements of the associator","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(tildeX_d(fκλ)^abc)^ α_abc X_d(eμν)^abc = δ_dd F^abc_d_(eμν)^(fκλ) mathrmid_d","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the left hand side represents a map in mathrmHom(dd), which must be zero if d is different from d, hence the δ_dd on the right hand side. In a strict category, or in the graphical notation, the associator α is omitted and these relations thus represent a unitary basis transform between the basis of inclusion maps X_d(eμν)^abc and tildeX_d(fκλ)^abc, which is also called an F-move, i.e. graphically:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Fmove)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The matrix F^abc_d is thus a unitary matrix. The pentagon coherence equation can also be rewritten in terms of these matrix elements, and as such yields the celebrated pentagon equation for the F-symbols. In a similar fashion, the unitors result in N^a1_b = N^1a_b = δ^a_b (where we have now written 1 instead of I for the unit object) and the triangle equation leads to additional relations between the F- symbols involving the unit object. In particular, if we identify X^1a_a1a(1a) with λ_a^ and X^a1_a1a(a1) with ρ_a^, the triangle equation and its collaries imply that F^1ab_c_(11μ)^(cν1) = δ^ν_μ, and similar relations for F^a1b_c and F^ab1_c, which are graphically represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Fmove1)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of group representations, i.e. the category mathbfRep_mathsfG, the splitting and fusion tensors are known as the Clebsch-Gordan coefficients, especially in the case of mathsfSU_2. An F-move amounts to a recoupling and the F-symbols can thus be identified with the 6j-symbols (strictly speaking, Racah's W-symbol for mathsfSU_2).","category":"page"},{"location":"man/categories/#Duality-and-pivotal-structure","page":"Optional introduction to category theory","title":"Duality and pivotal structure","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Next up is duality. Since we are assuming a dagger category, it can be assumed pivotal, where the left dual objects are identical to the right dual objects, and the left and right (co)evaluation are related via the dagger. We have already pointed out above that the dual object a^* of a simple object a is simple, and thus, it must be isomorphic to one of the representives bara of the different isomorphism classes of simple objects that we have chosen. Note that it can happen that bara=a. Duality implies an isomorphism between mathrmHom(WV) and mathrmHom(IVW^*), and thus, for a simple object a, mathrmEnd(a) 𝕜 is isomorphic to mathrmHom(1aa^*), such that the latter is also isomorphic to 𝕜, or thus N^abara_1 = 1. Also, all possible duals of a must be isomorphic, and thus there is a single representive bara, meaning that N^ab_1 = δ^bbara, i.e. for all other b bara, mathrmHom(1ab) mathrmHom(b^*a) = 0. Note that also barbara=a.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Let us now be somewhat careful with respect to the isomorphism between a^* and bara. If bara a, we can basically choose the representative of that isomorphism class as bara = a^*. However, this choice might not be valid if bara=a, as in that case the choice is already fixed, and might be different from a. To give a concrete example, the j=12 representation of mathsfSU_2 has a dual (contragradient, but because of unitarity, complex conjugated) representation which is isomorphic to itself, but not equal. In the context of tensors in quantum physics, we would like to be able to represent this representation and its conjugate, so we need to take the distinction and the isomorphism between them into account. This means that mathrmHom(a^*bara) is isomorphic to 𝕜 and contains a single linearly independent element, Z_a, which is a unitary isomorphism such that Z_a^dagger Z_a = mathrmid_a^* and Z_a Z_a^dagger = mathrmid_bara. Using the transpose, we obtain Z_a^* mathrmHom(bara^*a), and thus it is proportional to Z_bara, i.e. Z_a^* = χ_a Z_bara with χ_a a complex phase (assuming 𝕜 = ℂ). Another transpose results in Z_bara^* = χ_bara Z_a with χ_bara = overlineχ_a, where bar of a scalar quantity denotes its complex conjugate to avoid confusion with the transpose functor. If aand bara are distinct, we can essentially choose Z_bara such that χ_a is 1. However, for a=bara, the value of χ_a cannot be changed, but must satisfy χ_a^2 = 1, or thus χ_a = 1. This value is a topological invariant known as the Frobenius-Schur indicator. Graphically, we represent this isomorphism and its relations as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Zisomorphism)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We can now discuss the relation between the exact pairing and the fusion and splitting tensors. Given that the (left) coevaluation η_a mathrmHom(1 aa^*), we can define the splitting tensor as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"X^abara_1 = frac1sqrtd_a(mathrmid_a Z_a) η_a = frac1sqrtd_a(Z_a^* mathrmid_bara) tildeη_bara mathrmHom(1 abara)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The prefactor takes care of normalization, i.e. with η_a^ = tildeϵ_a, we find η_a^ η_a = tildeϵ_a η_a = mathrmtr(mathrmid_a) = d_a mathrmid_1, and thus (X^abara_1)^ X^abara_1 = mathrmid_1. Here, we have denoted d_a = mathrmdim(a) = mathrmtr(mathrmid_a) for the quantum dimension of the simple objects a. With this information, we can then compute F^abaraa_a, which has a single element (it's a 1 1 matrix), and find F^abaraa_a = fracχ_ad_a, where we've used tildeη_a = ϵ_a^ and the snake rules. Hence, both the quantum dimensions and the Frobenius-Schur indicator are encoded in the F-symbol. Hence, they do not represent new independent data. Again, the graphical representation is more enlightning:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: ZtoF)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"With these definitions, we can now also evaluate the action of the evaluation map on the splitting tensors, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: splittingfusionrelation)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where again bar denotes complex conjugation in the second line, and we introduced two new families of matrices A^ab_c and B^ab_c, whose entries are composed out of entries of the F-symbol, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A^ab_c^nu_mu = sqrtfracd_a d_bd_c χ_bara overlineF^baraab_b_(111)^(cμν)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"B^ab_c^nu_mu = sqrtfracd_a d_bd_c F^abbarb_a^(111)_(cμν)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Composing the left hand side of first graphical equation with its dagger, and noting that the resulting element f mathrmEnd(a) must satisfy f = d_a^-1 mathrmtr(f) mathrmid_a, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Brelation)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"allows to conclude that _ν B^ab_c^ν_μ overlineB^ab_c^ν_μ = delta_μμ, i.e. B^ab_c is a unitary matrix. The same result follows for A^ab_c in analogue fashion.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"note: Note\nIn the context of fusion categories, one often resorts to the so-called isotopic normalization convention, where splitting tensors are normalized as (X^ab_cμ)^ X^ab_cmu = sqrtfracd_a d_bd_c δ_cc δ_μμ mathrmid_c. This kills some of the quantum dimensions in formulas like the ones above and essentially allows to rotate the graphical notation of splitting and fusion tensors (up to a unitary transformation). Nonetheless, for our implementation of tensors and manipulations thereof (in particular orthonormal factorizations such as the singular value decomposition), we find it more convenient to work with the original normalization convention.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Let us again study in more detail the example mathbfRep_mathsfG. The quantum dimension d_a of an irrep a is just the normal vector space dimension (over 𝕜) of the space on which the irrep acts. The dual of an irrep a is its contragradient representation, which in the case of unitary representations amounts to the complex conjugate representation. This representation can be isomorphic to an already defined irrep bara, for example a itself. If that happens, it does not automatically imply that the irrep a is real-valued. For example, all irreps of mathsfSU_2 are self- dual, with the isomorphism given by a π rotation over the y-axis (in the standard basis). The resulting Frobenius-Schur indicator is +1 for integer spin irreps, and -1 for half-integer spin irreps. The value χ_a=+1 indicates that the representation can be made real, e.g. the integer spin representations can be written as tensor representations of mathsfSO_3 by a change of basis. The value χ_a=-1 indicates that the representation is quaternionic and cannot be made real.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The (co)evaluation expresses that the standard contraction of a vector with a dual vector yields a scalar, i.e. a representation and its dual (the contragradient) yields the trivial representation when correctly contracted. The coevaluation together with the isomorphism between the conjugate of irrep a and some irrep bara yields a way to define the Clebsch-Gordan coefficients (i.e. the splitting and fusion tensor) for fusing a bara to the trivial irrep, i.e. to what is called a singlet in the case of mathsfSU_2.","category":"page"},{"location":"man/categories/#Braidings-and-twists","page":"Optional introduction to category theory","title":"Braidings and twists","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, we can study the braiding structure of a pivotal fusion category. Not all fusion categories have a braiding structure. The existence of a braiding isomorphism τ_VWVWWV requires at the very least that N^ab_c = N^ba_c at the level of the simple objects. We can then express τ_ab in terms of its matrix elements as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"τ_ab X^ab_cμ = _ν R^ab_c^ν_μ X^ba_cν","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braidingR)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The hexagon coherence axiom for the braiding and the associator can then be reexpressed in terms of the F-symbols and R-symbols.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We can now compute the twist, which for simple objects needs to be scalars (or in fact complex phases because of unitarity) multiplying the identity morphism, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_a = mathrmid_a sum_bμ fracd_bd_a R^aa_b^μ_μ","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: simpletwist)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Henceforth, we reserve θ_a for the scalar value itself. Note that θ_a = θ_bara as our category is spherical and thus a ribbon category, and that the defining relation of a twist implies","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"R^ba_c^κ_μ R^ab_c^μ_ν = fractheta_cθ_a θ_b δ^κ_ν","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If a = bara, we can furthermore relate the twist, the braiding and the Frobenius- Schur indicator via θ_a χ_a R^aa_1 =1, because of","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: twistfrobeniusschur)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For the recurring example of mathbfRep_mathsfG, the braiding acts simply as the swap of the two vector spaces on which the representations are acting and is thus symmetric, i.e. τ_ba τ_ab = mathrmid_ab. All the twists are simply θ_a = 1. For an irrep that is self-dual, i.e. bara=a, the final expression simplifies to R^aa_1 = χ_a and thus states that the fusion from a a to the trivial sector is either symmetric under swaps if χ_a=1 or antisymmetric if χ_a=-1. For the case of mathsfSU_2, the coupling of two spin j states to a singlet it symmetric for integer j and odd for half-integer j.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"With this, we conclude our exposition of unitary fusion categories. There are many fusion categories that do not originate from the representation theory of groups, but are related to quantum groups and the representation theory of quasi-triangular Hopf algebras. They have non-integer quantum dimensions and generically admit for braidings which are not symmetric. A particular class of interesting fusion categories are modular fusion categories, which provide the mathematical structure for the theory of anyons and topological sectors in topological quantum states of matter. Thereto, one defines the modular S matrix, defined as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"S_ab = frac1D mathrmtr(τ_ab τ_ba) = frac1D _c N^ab_c d_c fracθ_cθ_a θ_b","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The normalization constant is given by D = sqrtsum_a d_a^2, and thus truly requires a fusion category with a finite number of (isomorphism classes of) simple objects. For a modular fusion category, the symmetric matrix S is non-degenerate, and in fact (for a unitary fusion category) unitary. Note, however, that for a symmetric braiding S_ab = fracd_a d_bD and thus S is a rank 1 matrix. In particular, mathbfRep_mathsfG is never a modular category and the properties associated with this are not of (direct) importance for TensorKit.jl. We refer to the references for further information about modular categories.","category":"page"},{"location":"man/categories/#Bibliography","page":"Optional introduction to category theory","title":"Bibliography","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[turaev]: Turaev, V. G., & Virelizier, A. (2017). Monoidal categories and topological field theory (Vol. 322).\n Birkhäuser.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[selinger]: Selinger, P. (2010). A survey of graphical languages for monoidal categories.\n In New structures for physics (pp. 289-355). Springer, Berlin, Heidelberg.\n [https://arxiv.org/abs/0908.3347](https://arxiv.org/abs/0908.3347)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[kassel]: Kassel, C. (2012). Quantum groups (Vol. 155).\n Springer Science & Business Media.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[kitaev]: Kitaev, A. (2006). Anyons in an exactly solved model and beyond.\n Annals of Physics, 321(1), 2-111.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[beer]: From categories to anyons: a travelogue\n Kerstin Beer, Dmytro Bondarenko, Alexander Hahn, Maria Kalabakov, Nicole Knust, Laura Niermann, Tobias J. Osborne, Christin Schridde, Stefan Seckmeyer, Deniz E. Stiegemann, and Ramona Wolf\n [https://arxiv.org/abs/1811.06670](https://arxiv.org/abs/1811.06670)","category":"page"}] +[{"location":"lib/tensors/#Tensors","page":"Tensors","title":"Tensors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/tensors/#Type-hierarchy","page":"Tensors","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The abstract supertype of all tensors in TensorKit is given by AbstractTensorMap:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"AbstractTensorMap","category":"page"},{"location":"lib/tensors/#TensorKit.AbstractTensorMap","page":"Tensors","title":"TensorKit.AbstractTensorMap","text":"abstract type AbstractTensorMap{T<:Number, S<:IndexSpace, N₁, N₂} end\n\nAbstract supertype of all tensor maps, i.e. linear maps between tensor products of vector spaces of type S<:IndexSpace, with element type T. An AbstractTensorMap maps from an input space of type ProductSpace{S, N₂} to an output space of type ProductSpace{S, N₁}.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The following concrete subtypes are provided within the TensorKit library:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap\nDiagonalTensorMap\nAdjointTensorMap\nBraidingTensor","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap","page":"Tensors","title":"TensorKit.TensorMap","text":"struct TensorMap{T, S<:IndexSpace, N₁, N₂, A<:DenseVector{T}} <: AbstractTensorMap{T, S, N₁, N₂}\n\nSpecific subtype of AbstractTensorMap for representing tensor maps (morphisms in a tensor category), where the data is stored in a dense vector.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.DiagonalTensorMap","page":"Tensors","title":"TensorKit.DiagonalTensorMap","text":"DiagonalTensorMap{T}(undef, domain::S) where {T,S<:IndexSpace}\n# expert mode: select storage type `A`\nDiagonalTensorMap{T,S,A}(undef, domain::S) where {T,S<:IndexSpace,A<:DenseVector{T}}\n\nConstruct a DiagonalTensorMap with uninitialized data.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.AdjointTensorMap","page":"Tensors","title":"TensorKit.AdjointTensorMap","text":"struct AdjointTensorMap{T, S, N₁, N₂, TT<:AbstractTensorMap} <: AbstractTensorMap{T, S, N₁, N₂}\n\nSpecific subtype of AbstractTensorMap that is a lazy wrapper for representing the adjoint of an instance of AbstractTensorMap.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.BraidingTensor","page":"Tensors","title":"TensorKit.BraidingTensor","text":"struct BraidingTensor{T,S<:IndexSpace} <: AbstractTensorMap{T, S, 2, 2}\nBraidingTensor(V1::S, V2::S, adjoint::Bool=false) where {S<:IndexSpace}\n\nSpecific subtype of AbstractTensorMap for representing the braiding tensor that braids the first input over the second input; its inverse can be obtained as the adjoint.\n\nIt holds that domain(BraidingTensor(V1, V2)) == V1 ⊗ V2 and codomain(BraidingTensor(V1, V2)) == V2 ⊗ V1.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Of those, TensorMap provides the generic instantiation of our tensor concept. It supports various constructors, which are discussed in the next subsection.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Furthermore, some aliases are provided for convenience:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"AbstractTensor\nTensor","category":"page"},{"location":"lib/tensors/#TensorKit.AbstractTensor","page":"Tensors","title":"TensorKit.AbstractTensor","text":"AbstractTensor{T,S,N} = AbstractTensorMap{T,S,N,0}\n\nAbstract supertype of all tensors, i.e. elements in the tensor product space of type ProductSpace{S, N}, with element type T.\n\nAn AbstractTensor{T, S, N} is actually a special case AbstractTensorMap{T, S, N, 0}, i.e. a tensor map with only non-trivial output spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorKit.Tensor","page":"Tensors","title":"TensorKit.Tensor","text":"Tensor{T, S, N, A<:DenseVector{T}} = TensorMap{T, S, N, 0, A}\n\nSpecific subtype of AbstractTensor for representing tensors whose data is stored in a dense vector.\n\nA Tensor{T, S, N, A} is actually a special case TensorMap{T, S, N, 0, A}, i.e. a tensor map with only a non-trivial output space.\n\n\n\n\n\n","category":"type"},{"location":"lib/tensors/#TensorMap-constructors","page":"Tensors","title":"TensorMap constructors","text":"","category":"section"},{"location":"lib/tensors/#General-constructors","page":"Tensors","title":"General constructors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"A TensorMap with undefined data can be constructed by specifying its domain and codomain:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap{T}(::UndefInitializer, V::TensorMapSpace{S,N₁,N₂}) where {T,S,N₁,N₂}","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{UndefInitializer, HomSpace{S, ProductSpace{S, N₁}, ProductSpace{S, N₂}}}} where {T, S, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap{T}(undef, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂})\n where {T,S,N₁,N₂}\nTensorMap{T}(undef, codomain ← domain)\nTensorMap{T}(undef, domain → codomain)\n# expert mode: select storage type `A`\nTensorMap{T,S,N₁,N₂,A}(undef, codomain ← domain)\nTensorMap{T,S,N₁,N₂,A}(undef, domain → domain)\n\nConstruct a TensorMap with uninitialized data.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The resulting object can then be filled with data using the setindex! method as discussed below, using functions such as VectorInterface.zerovector!, rand! or fill!, or it can be used as an output argument in one of the many methods that accept output arguments, or in an @tensor output[...] = ... expression.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Alternatively, a TensorMap can be constructed by specifying its data, codmain and domain in one of the following ways:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, V::TensorMapSpace{S,N₁,N₂}) where {S,N₁,N₂}\nTensorMap(data::AbstractArray, V::TensorMapSpace{S,N₁,N₂}; tol) where {S<:IndexSpace,N₁,N₂}","category":"page"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{AbstractDict{<:Sector, <:AbstractMatrix}, HomSpace{S, ProductSpace{S, N₁}, ProductSpace{S, N₂}}}} where {S, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap(data::AbstractDict{<:Sector,<:AbstractMatrix}, codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S<:ElementarySpace,N₁,N₂}\nTensorMap(data, codomain ← domain)\nTensorMap(data, domain → codomain)\n\nConstruct a TensorMap by explicitly specifying its block data.\n\nArguments\n\ndata::AbstractDict{<:Sector,<:AbstractMatrix}: dictionary containing the block data for each coupled sector c as a matrix of size (blockdim(codomain, c), blockdim(domain, c)).\ncodomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.\ndomain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.\n\nAlternatively, the domain and codomain can be specified by passing a HomSpace using the syntax codomain ← domain or domain → codomain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.TensorMap-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{AbstractArray, TensorMapSpace{S, N₁, N₂}}} where {S<:ElementarySpace, N₁, N₂}","page":"Tensors","title":"TensorKit.TensorMap","text":"TensorMap(data::AbstractArray, codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂};\n tol=sqrt(eps(real(float(eltype(data)))))) where {S<:ElementarySpace,N₁,N₂}\nTensorMap(data, codomain ← domain; tol=sqrt(eps(real(float(eltype(data))))))\nTensorMap(data, domain → codomain; tol=sqrt(eps(real(float(eltype(data))))))\n\nConstruct a TensorMap from a plain multidimensional array.\n\nArguments\n\ndata::DenseArray: tensor data as a plain array.\ncodomain::ProductSpace{S,N₁}: the codomain as a ProductSpace of N₁ spaces of type S<:ElementarySpace.\ndomain::ProductSpace{S,N₂}: the domain as a ProductSpace of N₂ spaces of type S<:ElementarySpace.\ntol=sqrt(eps(real(float(eltype(data)))))::Float64: \n\nHere, data can be specified in three ways:\n\ndata can be a DenseVector of length dim(codomain ← domain); in that case it represents the actual independent entries of the tensor map. An instance will be created that directly references data.\ndata can be an AbstractMatrix of size (dim(codomain), dim(domain))\ndata can be an AbstractArray of rank N₁ + N₂ with a size matching that of the domain and codomain spaces, i.e. size(data) == (dims(codomain)..., dims(domain)...)\n\nIn case 2 and 3, the TensorMap constructor will reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t), up to an error specified by tol. For the case where sectortype(S) == Trivial and data isa DenseArray, the data array is simply reshaped into a vector and used as in case 1 so that the memory will still be shared. In other cases, new memory will be allocated.\n\nNote that in the case of N₁ + N₂ = 1, case 3 also amounts to data being a vector, whereas when N₁ + N₂ == 2, case 2 and case 3 both require data to be a matrix. Such ambiguous cases are resolved by checking the size of data in an attempt to support all possible cases.\n\nnote: Note\nThis constructor for case 2 and 3 only works for sectortype values for which conversion to a plain array is possible, and only in the case where the data actually respects the specified symmetry structure, up to a tolerance tol.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Finally, we also support the following Array-like constructors","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"zeros(::Type, V::TensorMapSpace)\nones(::Type, V::TensorMapSpace)\nrand(::Type, V::TensorMapSpace)\nrandn(::Type, V::TensorMapSpace)\nRandom.randexp(::Type, V::TensorMapSpace)","category":"page"},{"location":"lib/tensors/#Base.zeros-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.zeros","text":"zeros([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}\nzeros([T=Float64,], codomain ← domain)\n\nCreate a TensorMap with element type T, of all zeros with spaces specified by codomain and domain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.ones-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.ones","text":"ones([T=Float64,], codomain::ProductSpace{S,N₁}, domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T}\nones([T=Float64,], codomain ← domain)\n\nCreate a TensorMap with element type T, of all ones with spaces specified by codomain and domain.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.rand-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.rand","text":"rand([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrand([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by rand.\n\nSee also (rand)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.randn-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Base.randn","text":"randn([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrandn([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by randn.\n\nSee also (randn)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Random.randexp-Tuple{Type, TensorMapSpace}","page":"Tensors","title":"Random.randexp","text":"randexp([rng=default_rng()], [T=Float64], codomain::ProductSpace{S,N₁},\n domain::ProductSpace{S,N₂}) where {S,N₁,N₂,T} -> t\nrandexp([rng=default_rng()], [T=Float64], codomain ← domain) -> t\n\nGenerate a tensor t with entries generated by randexp.\n\nSee also (randexp)!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"as well as a similar constructor","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.similar(::AbstractTensorMap, args...)","category":"page"},{"location":"lib/tensors/#Base.similar-Tuple{AbstractTensorMap, Vararg{Any}}","page":"Tensors","title":"Base.similar","text":"similar(t::AbstractTensorMap, [AorT=storagetype(t)], [V=space(t)])\nsimilar(t::AbstractTensorMap, [AorT=storagetype(t)], codomain, domain)\n\nCreates an uninitialized mutable tensor with the given scalar or storagetype AorT and structure V or codomain ← domain, based on the source tensormap. The second and third arguments are both optional, defaulting to the given tensor's storagetype and space. The structure may be specified either as a single HomSpace argument or as codomain and domain.\n\nBy default, this will result in TensorMap{T}(undef, V) when custom objects do not specialize this method.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Specific-constructors","page":"Tensors","title":"Specific constructors","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Additionally, the following methods can be used to construct specific TensorMap instances.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"id\nisomorphism\nunitary\nisometry","category":"page"},{"location":"lib/tensors/#TensorKit.id","page":"Tensors","title":"TensorKit.id","text":"id([T::Type=Float64,] V::TensorSpace) -> TensorMap\n\nConstruct the identity endomorphism on space V, i.e. return a t::TensorMap with domain(t) == codomain(t) == V, where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.isomorphism","page":"Tensors","title":"TensorKit.isomorphism","text":"isomorphism([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nisomorphism([T::Type=Float64,] codomain ← domain) -> TensorMap\nisomorphism([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific isomorphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, an error will be thrown.\n\nnote: Note\nThere is no canonical choice for a specific isomorphism, but the current choice is such that isomorphism(cod, dom) == inv(isomorphism(dom, cod)).\n\nSee also unitary when InnerProductStyle(cod) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.unitary","page":"Tensors","title":"TensorKit.unitary","text":"unitary([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nunitary([T::Type=Float64,] codomain ← domain) -> TensorMap\nunitary([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific unitary morphism between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. If the spaces are not isomorphic, or the spacetype does not have a Euclidean inner product, an error will be thrown.\n\nnote: Note\nThere is no canonical choice for a specific unitary, but the current choice is such that unitary(cod, dom) == inv(unitary(dom, cod)) = adjoint(unitary(dom, cod)).\n\nSee also isomorphism and isometry.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.isometry","page":"Tensors","title":"TensorKit.isometry","text":"isometry([T::Type=Float64,] codomain::TensorSpace, domain::TensorSpace) -> TensorMap\nisometry([T::Type=Float64,] codomain ← domain) -> TensorMap\nisometry([T::Type=Float64,] domain → codomain) -> TensorMap\n\nConstruct a specific isometry between the codomain and the domain, i.e. return a t::TensorMap where either scalartype(t) = T if T is a Number type or storagetype(t) = T if T is a DenseVector type. The isometry t then satisfies t' * t = id(domain) and (t * t')^2 = t * t'. If the spaces do not allow for such an isometric inclusion, an error will be thrown.\n\nSee also isomorphism and unitary.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#AbstractTensorMap-properties-and-data-access","page":"Tensors","title":"AbstractTensorMap properties and data access","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The following methods exist to obtain type information:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.eltype(::Type{<:AbstractTensorMap{T}}) where {T}\nspacetype(::Type{<:AbstractTensorMap{<:Any,S}}) where {S}\nsectortype(::Type{TT}) where {TT<:AbstractTensorMap}\nfield(::Type{TT}) where {TT<:AbstractTensorMap}\nstoragetype","category":"page"},{"location":"lib/tensors/#Base.eltype-Union{Tuple{Type{<:AbstractTensorMap{T}}}, Tuple{T}} where T","page":"Tensors","title":"Base.eltype","text":"eltype(::AbstractTensorMap) -> Type{T}\neltype(::Type{<:AbstractTensorMap}) -> Type{T}\n\nReturn the scalar or element type T of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.spacetype-Union{Tuple{Type{<:AbstractTensorMap{<:Any, S}}}, Tuple{S}} where S","page":"Tensors","title":"TensorKit.spacetype","text":"spacetype(::AbstractTensorMap) -> Type{S<:IndexSpace}\nspacetype(::Type{<:AbstractTensorMap}) -> Type{S<:IndexSpace}\n\nReturn the type of the elementary space S of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.sectortype-Union{Tuple{Type{TT}}, Tuple{TT}} where TT<:AbstractTensorMap","page":"Tensors","title":"TensorKit.sectortype","text":"sectortype(::AbstractTensorMap) -> Type{I<:Sector}\nsectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}\n\nReturn the type of sector I of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.field-Union{Tuple{Type{TT}}, Tuple{TT}} where TT<:AbstractTensorMap","page":"Tensors","title":"TensorKit.field","text":"field(::AbstractTensorMap) -> Type{𝔽<:Field}\nfield(::Type{<:AbstractTensorMap}) -> Type{𝔽<:Field}\n\nReturn the type of field 𝔽 of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.storagetype","page":"Tensors","title":"TensorKit.storagetype","text":"storagetype(t::AbstractTensorMap) -> Type{A<:AbstractVector}\nstoragetype(T::Type{<:AbstractTensorMap}) -> Type{A<:AbstractVector}\n\nReturn the type of vector that stores the data of a tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To obtain information about the indices, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"space(::AbstractTensorMap, ::Int)\ndomain\ncodomain\nnumin\nnumout\nnumind\ncodomainind\ndomainind\nallind","category":"page"},{"location":"lib/tensors/#TensorKit.space-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKit.space","text":"space(t::AbstractTensorMap{T,S,N₁,N₂}) -> HomSpace{S,N₁,N₂}\nspace(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nThe index information of a tensor, i.e. the HomSpace of its domain and codomain. If i is specified, return the i-th index space.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.domain","page":"Tensors","title":"TensorKit.domain","text":"domain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₂}\ndomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nReturn the domain of a tensor, i.e. the product space of the input spaces. If i is specified, return the i-th input space. Implementations should provide domain(t).\n\nSee also codomain and space.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.codomain","page":"Tensors","title":"TensorKit.codomain","text":"codomain(t::AbstractTensorMap{T,S,N₁,N₂}) -> ProductSpace{S,N₁}\ncodomain(t::AbstractTensorMap{T,S,N₁,N₂}, i::Int) -> S\n\nReturn the codomain of a tensor, i.e. the product space of the output spaces. If i is specified, return the i-th output space. Implementations should provide codomain(t).\n\nSee also domain and space.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numin","page":"Tensors","title":"TensorKit.numin","text":"numin(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int\n\nReturn the number of input spaces of a tensor. This is equivalent to the number of spaces in the domain of that tensor.\n\nSee also numout and numind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numout","page":"Tensors","title":"TensorKit.numout","text":"numout(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Int\n\nReturn the number of output spaces of a tensor. This is equivalent to the number of spaces in the codomain of that tensor.\n\nSee also numin and numind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.numind","page":"Tensors","title":"TensorKit.numind","text":"numind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Int\n\nReturn the total number of input and output spaces of a tensor. This is equivalent to the total number of spaces in the domain and codomain of that tensor.\n\nSee also numout and numin.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.codomainind","page":"Tensors","title":"TensorKit.codomainind","text":"codomainind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of the codomain of a tensor.\n\nSee also domainind and allind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.domainind","page":"Tensors","title":"TensorKit.domainind","text":"domainind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of the domain of a tensor.\n\nSee also codomainind and allind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.allind","page":"Tensors","title":"TensorKit.allind","text":"allind(::Union{TT,Type{TT}}) where {TT<:AbstractTensorMap} -> Tuple{Int}\n\nReturn all indices of a tensor, i.e. the indices of its domain and codomain.\n\nSee also codomainind and domainind.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"In TensorMap instances, all data is gathered in a single AbstractVector, which has an internal structure into blocks associated to total coupled charge, within which live subblocks associated with the different possible fusion-splitting tree pairs.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To obtain information about the structure of the data, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"fusionblockstructure(::AbstractTensorMap)\ndim(::AbstractTensorMap)\nblocksectors(::AbstractTensorMap)\nhasblock(::AbstractTensorMap, ::Sector)\nfusiontrees(t::AbstractTensorMap)","category":"page"},{"location":"lib/tensors/#TensorKit.fusionblockstructure-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.fusionblockstructure","text":"fusionblockstructure(t::AbstractTensorMap) -> TensorStructure\n\nReturn the necessary structure information to decompose a tensor in blocks labeled by coupled sectors and in subblocks labeled by a splitting-fusion tree couple.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKitSectors.dim-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKitSectors.dim","text":"dim(t::AbstractTensorMap) -> Int\n\nThe total number of free parameters of a tensor, discounting the entries that are fixed by symmetry. This is also the dimension of the HomSpace on which the TensorMap is defined.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.blocksectors-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.blocksectors","text":"blocksectors(t::AbstractTensorMap)\n\nReturn an iterator over all coupled sectors of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.hasblock-Tuple{AbstractTensorMap, Sector}","page":"Tensors","title":"TensorKit.hasblock","text":"hasblock(t::AbstractTensorMap, c::Sector) -> Bool\n\nVerify whether a tensor has a block corresponding to a coupled sector c.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.fusiontrees-Tuple{AbstractTensorMap}","page":"Tensors","title":"TensorKit.fusiontrees","text":"fusiontrees(t::AbstractTensorMap)\n\nReturn an iterator over all splitting - fusion tree pairs of a tensor.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Data can be accessed (and modified) in a number of ways. To access the full matrix block associated with the coupled charges, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"block\nblocks","category":"page"},{"location":"lib/tensors/#TensorKit.block","page":"Tensors","title":"TensorKit.block","text":"block(t::AbstractTensorMap, c::Sector)\n\nReturn the matrix block of a tensor corresponding to a coupled sector c.\n\nSee also blocks, blocksectors, blockdim and hasblock.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.blocks","page":"Tensors","title":"TensorKit.blocks","text":"blocks(t::AbstractTensorMap)\n\nReturn an iterator over all blocks of a tensor, i.e. all coupled sectors and their corresponding matrix blocks.\n\nSee also block, blocksectors, blockdim and hasblock.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"To access the data associated with a specific fusion tree pair, you can use:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::TensorMap{T,S,N₁,N₂}, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}\nBase.setindex!(::TensorMap{T,S,N₁,N₂}, ::Any, ::FusionTree{I,N₁}, ::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}","category":"page"},{"location":"lib/tensors/#Base.getindex-Union{Tuple{I}, Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{TensorMap{T, S, N₁, N₂, A} where A<:DenseVector{T}, FusionTree{I, N₁}, FusionTree{I, N₂}}} where {T, S, N₁, N₂, I<:Sector}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::TensorMap{T,S,N₁,N₂,I},\n f₁::FusionTree{I,N₁},\n f₂::FusionTree{I,N₂}) where {T,SN₁,N₂,I<:Sector}\n -> StridedViews.StridedView\nt[f₁, f₂]\n\nReturn a view into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). In particular, if f₁.coupled == f₂.coupled == c, then a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) is returned which represents the slice of block(t, c) whose row indices correspond to f₁.uncoupled and column indices correspond to f₂.uncoupled.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.setindex!-Union{Tuple{I}, Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{T}, Tuple{TensorMap{T, S, N₁, N₂, A} where A<:DenseVector{T}, Any, FusionTree{I, N₁}, FusionTree{I, N₂}}} where {T, S, N₁, N₂, I<:Sector}","page":"Tensors","title":"Base.setindex!","text":"Base.setindex!(t::TensorMap{T,S,N₁,N₂,I},\n v,\n f₁::FusionTree{I,N₁},\n f₂::FusionTree{I,N₂}) where {T,S,N₁,N₂,I<:Sector}\nt[f₁, f₂] = v\n\nCopies v into the data slice of t corresponding to the splitting - fusion tree pair (f₁, f₂). Here, v can be any object that can be copied into a StridedViews.StridedView of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)) using Base.copy!.\n\nSee also Base.getindex(::TensorMap{T,S,N₁,N₂,I<:Sector}, ::FusionTree{I<:Sector,N₁}, ::FusionTree{I<:Sector,N₂})\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"For a tensor t with FusionType(sectortype(t)) isa UniqeFuison, fusion trees are completely determined by the outcoming sectors, and the data can be accessed in a more straightforward way:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::TensorMap, ::Tuple{I,Vararg{I}}) where {I<:Sector}","category":"page"},{"location":"lib/tensors/#Base.getindex-Union{Tuple{I}, Tuple{TensorMap, Tuple{I, Vararg{I}}}} where I<:Sector","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::TensorMap\n sectors::NTuple{N₁+N₂,I}) where {N₁,N₂,I<:Sector} \n -> StridedViews.StridedView\nt[sectors]\n\nReturn a view into the data slice of t corresponding to the splitting - fusion tree pair with combined uncoupled charges sectors. In particular, if sectors == (s₁..., s₂...) where s₁ and s₂ correspond to the coupled charges in the codomain and domain respectively, then a StridedViews.StridedView of size (dims(codomain(t), s₁)..., dims(domain(t), s₂)) is returned.\n\nThis method is only available for the case where FusionStyle(I) isa UniqueFusion, since it assumes a uniquely defined coupled charge.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"For tensor t with sectortype(t) == Trivial, the data can be accessed and manipulated directly as multidimensional arrays:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"Base.getindex(::AbstractTensorMap)\nBase.getindex(::AbstractTensorMap, ::Vararg{SliceIndex})\nBase.setindex!(::AbstractTensorMap, ::Any, ::Vararg{SliceIndex})","category":"page"},{"location":"lib/tensors/#Base.getindex-Tuple{AbstractTensorMap}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::AbstractTensorMap)\nt[]\n\nReturn a view into the data of t as a StridedViews.StridedView of size (dims(codomain(t))..., dims(domain(t))...).\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.getindex-Tuple{AbstractTensorMap, Vararg{Union{Colon, AbstractRange{<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}}","page":"Tensors","title":"Base.getindex","text":"Base.getindex(t::AbstractTensorMap, indices::Vararg{Int})\nt[indices]\n\nReturn a view into the data slice of t corresponding to indices, by slicing the StridedViews.StridedView into the full data array.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.setindex!-Tuple{AbstractTensorMap, Any, Vararg{Union{Colon, AbstractRange{<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}}","page":"Tensors","title":"Base.setindex!","text":"Base.setindex!(t::AbstractTensorMap, v, indices::Vararg{Int})\nt[indices] = v\n\nAssigns v to the data slice of t corresponding to indices.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#AbstractTensorMap-operations","page":"Tensors","title":"AbstractTensorMap operations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The operations that can be performed on an AbstractTensorMap can be organized into the following categories:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"vector operations: these do not change the space or index strucure of a tensor and can be straightforwardly implemented on on the full data. All the methods described in VectorInterface.jl are supported. For compatibility reasons, we also provide implementations for equivalent methods from LinearAlgebra.jl, such as axpy!, axpby!.\nindex manipulations: these change (permute) the index structure of a tensor, which affects the data in a way that is fully determined by the categorical data of the sectortype of the tensor.\n(planar) contractions and (planar) traces (i.e., contractions with identity tensors). Tensor contractions correspond to a combination of some index manipulations followed by a composition or multiplication of the tensors in their role as linear maps. Tensor contractions are however of such important and frequency that they require a dedicated implementation.\ntensor factorisations, which relies on their identification of tensors with linear maps between tensor spaces. The factorisations are applied as ordinary matrix factorisations to the matrix blocks associated with the coupled charges.","category":"page"},{"location":"lib/tensors/#Index-manipulations","page":"Tensors","title":"Index manipulations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"A general index manipulation of a TensorMap object can be built up by considering some transformation of the fusion trees, along with a permutation of the stored data. They come in three flavours, which are either of the type transform(!) which are exported, or of the type add_transform!, for additional expert-mode options that allows for addition and scaling, as well as the selection of a custom backend.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"permute(::AbstractTensorMap, ::Index2Tuple{N₁,N₂}; ::Bool) where {N₁,N₂}\nbraid(::AbstractTensorMap, ::Index2Tuple, ::IndexTuple; ::Bool)\ntranspose(::AbstractTensorMap, ::Index2Tuple; ::Bool)\nrepartition(::AbstractTensorMap, ::Int, ::Int; ::Bool)\ntwist(::AbstractTensorMap, ::Int; ::Bool)\ninsertleftunit(::AbstractTensorMap, ::Int)\ninsertrightunit(::AbstractTensorMap, ::Int)","category":"page"},{"location":"lib/tensors/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {N₁, N₂}","page":"Tensors","title":"TensorKit.permute","text":"permute(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;\n copy::Bool=false)\n -> tdst::TensorMap\n\nReturn 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.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo permute into an existing destination, see permute! and add_permute!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.braid-Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}, NTuple{N, Int64} where N}","page":"Tensors","title":"TensorKit.braid","text":"braid(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple, levels::IndexTuple;\n copy::Bool = false)\n -> tdst::TensorMap\n\nReturn 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.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo braid into an existing destination, see braid! and add_braid!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#Base.transpose-Tuple{AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}}","page":"Tensors","title":"Base.transpose","text":"transpose(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;\n copy::Bool=false)\n -> tdst::TensorMap\n\nReturn 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))...).\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo permute into an existing destination, see permute! and add_permute!\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.repartition-Tuple{AbstractTensorMap, Int64, Int64}","page":"Tensors","title":"TensorKit.repartition","text":"repartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}\n -> tdst::AbstractTensorMap{S,N₁,N₂}\n\nReturn tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo repartition into an existing destination, see repartition!.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKitSectors.twist-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKitSectors.twist","text":"twist(tsrc::AbstractTensorMap, i::Int; inv::Bool=false) -> tdst\ntwist(tsrc::AbstractTensorMap, is; inv::Bool=false) -> tdst\n\nApply a twist to the ith index of tsrc and return the result as a new tensor. If inv=true, use the inverse twist.\n\nSee twist! for storing the result in place.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.insertleftunit-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKit.insertleftunit","text":"insertleftunit(tsrc::AbstractTensorMap, i::Int=numind(t) + 1;\n conj=false, dual=false, copy=false) -> tdst\n\nInsert a trivial vector space, isomorphic to the underlying field, at position i. More specifically, adds a left monoidal unit or its dual.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nSee also insertrightunit and removeunit.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.insertrightunit-Tuple{AbstractTensorMap, Int64}","page":"Tensors","title":"TensorKit.insertrightunit","text":"insertrightunit(tsrc::AbstractTensorMap, i::Int=numind(t);\n conj=false, dual=false, copy=false) -> tdst\n\nInsert a trivial vector space, isomorphic to the underlying field, after position i. More specifically, adds a right monoidal unit or its dual.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nSee also insertleftunit and removeunit.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"permute!(::AbstractTensorMap, ::AbstractTensorMap, ::Index2Tuple)\nbraid!\ntranspose!\nrepartition!\ntwist!","category":"page"},{"location":"lib/tensors/#Base.permute!-Tuple{AbstractTensorMap, AbstractTensorMap, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}} where {N₁, N₂}}","page":"Tensors","title":"Base.permute!","text":"permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple)\n -> tdst\n\nWrite into tdst the result of permuting the indices of tsrc. The codomain and domain of tdst correspond to the indices in p₁ and p₂ of tsrc respectively.\n\nSee permute for creating a new tensor and add_permute! for a more general version.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.braid!","page":"Tensors","title":"TensorKit.braid!","text":"braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple, levels::Tuple)\n -> tdst\n\nWrite into tdst the result of 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.\n\nSee braid for creating a new tensor and add_braid! for a more general version.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.transpose!","page":"Tensors","title":"LinearAlgebra.transpose!","text":"transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple)\n -> tdst\n\nWrite into tdst the result of 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))...).\n\nSee transpose for creating a new tensor and add_transpose! for a more general version.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.repartition!","page":"Tensors","title":"TensorKit.repartition!","text":"repartition!(tdst::AbstractTensorMap{S}, tsrc::AbstractTensorMap{S}) where {S} -> tdst\n\nWrite into tdst the result of repartitioning the indices of tsrc. This is just a special case of a transposition that only changes the number of in- and outgoing indices.\n\nSee repartition for creating a new tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.twist!","page":"Tensors","title":"TensorKit.twist!","text":"twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) -> t\ntwist!(t::AbstractTensorMap, is; inv::Bool=false) -> t\n\nApply a twist to the ith index of t, or all indices in is, storing the result in t. If inv=true, use the inverse twist.\n\nSee twist for creating a new tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TensorKit.add_permute!\nTensorKit.add_braid!\nTensorKit.add_transpose!","category":"page"},{"location":"lib/tensors/#TensorKit.add_permute!","page":"Tensors","title":"TensorKit.add_permute!","text":"add_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂).\n\nSee also permute, permute!, add_braid!, add_transpose!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.add_braid!","page":"Tensors","title":"TensorKit.add_braid!","text":"add_braid!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n levels::IndexTuple, α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after braiding the indices of tsrc according to (p₁, p₂) and levels.\n\nSee also braid, braid!, add_permute!, add_transpose!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.add_transpose!","page":"Tensors","title":"TensorKit.add_transpose!","text":"add_transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple,\n α::Number, β::Number, backend::AbstractBackend...)\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after transposing the indices of tsrc according to (p₁, p₂).\n\nSee also transpose, transpose!, add_permute!, add_braid!.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#Tensor-map-composition,-traces,-contractions-and-tensor-products","page":"Tensors","title":"Tensor map composition, traces, contractions and tensor products","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"compose(::AbstractTensorMap, ::AbstractTensorMap)\ntrace_permute!\ncontract!\n⊗(::AbstractTensorMap, ::AbstractTensorMap)","category":"page"},{"location":"lib/tensors/#TensorKit.compose-Tuple{AbstractTensorMap, AbstractTensorMap}","page":"Tensors","title":"TensorKit.compose","text":"compose(t1::AbstractTensorMap, t2::AbstractTensorMap) -> AbstractTensorMap\n\nReturn the AbstractTensorMap that implements the composition of the two tensor maps t1 and t2.\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorKit.trace_permute!","page":"Tensors","title":"TensorKit.trace_permute!","text":"trace_permute!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap,\n (p₁, p₂)::Index2Tuple, (q₁, q₂)::Index2Tuple,\n α::Number, β::Number, backend=TO.DefaultBackend())\n\nReturn the updated tdst, which is the result of adding α * tsrc to tdst after permuting the indices of tsrc according to (p₁, p₂) and furthermore tracing the indices in q₁ and q₂.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.contract!","page":"Tensors","title":"TensorKit.contract!","text":"contract!(C::AbstractTensorMap,\n A::AbstractTensorMap, (oindA, cindA)::Index2Tuple,\n B::AbstractTensorMap, (cindB, oindB)::Index2Tuple,\n (p₁, p₂)::Index2Tuple,\n α::Number, β::Number,\n backend, allocator)\n\nReturn the updated C, which is the result of adding α * A * B to C after permuting the indices of A and B according to (oindA, cindA) and (cindB, oindB) respectively.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKitSectors.:⊗-Tuple{AbstractTensorMap, AbstractTensorMap}","page":"Tensors","title":"TensorKitSectors.:⊗","text":"⊗(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap\notimes(t1::AbstractTensorMap, t2::AbstractTensorMap, ...) -> TensorMap\n\nCompute the tensor product between two AbstractTensorMap instances, which results in a new TensorMap instance whose codomain is codomain(t1) ⊗ codomain(t2) and whose domain is domain(t1) ⊗ domain(t2).\n\n\n\n\n\n","category":"method"},{"location":"lib/tensors/#TensorMap-factorizations","page":"Tensors","title":"TensorMap factorizations","text":"","category":"section"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The factorisation methods come in two flavors, namely a non-destructive version where you can specify an additional permutation of the domain and codomain indices before the factorisation is performed (provided that sectorstyle(t) has a symmetric braiding) as well as a destructive version The non-destructive methods are given first:","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"leftorth\nrightorth\nleftnull\nrightnull\ntsvd\neigh\neig\neigen\nisposdef","category":"page"},{"location":"lib/tensors/#TensorKit.leftorth","page":"Tensors","title":"TensorKit.leftorth","text":"leftorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R\n\nCreate orthonormal basis Q for indices in leftind, and remainder R such that permute(t, (leftind, rightind)) = Q*R.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftorth!(t, alg = QRpos()).\n\nDifferent algorithms are available, namely QR(), QRpos(), SVD() and Polar(). QR() and QRpos() use a standard QR decomposition, producing an upper triangular matrix R. Polar() produces a Hermitian and positive semidefinite R. QRpos() corrects the standard QR decomposition such that the diagonal elements of R are positive. Only QRpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.rightorth","page":"Tensors","title":"TensorKit.rightorth","text":"rightorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q\n\nCreate orthonormal basis Q for indices in rightind, and remainder L such that permute(t, (leftind, rightind)) = L*Q.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightorth!(t, alg = LQpos()).\n\nDifferent algorithms are available, namely LQ(), LQpos(), RQ(), RQpos(), SVD() and Polar(). LQ() and LQpos() produce a lower triangular matrix L and are computed using a QR decomposition of the transpose. RQ() and RQpos() produce an upper triangular remainder L and only works if the total left dimension is smaller than or equal to the total right dimension. LQpos() and RQpos() add an additional correction such that the diagonal elements of L are positive. Polar() produces a Hermitian and positive semidefinite L. Only LQpos(), RQpos() and Polar() are unique (no residual freedom) so that they always return the same result for the same input tensor t.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightorth(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.leftnull","page":"Tensors","title":"TensorKit.leftnull","text":"leftnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = QRpos()) -> N\n\nCreate orthonormal basis for the orthogonal complement of the support of the indices in leftind, such that N' * permute(t, (leftind, rightind)) = 0.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using leftnull!(t, alg = QRpos()).\n\nDifferent algorithms are available, namely QR() (or equivalently, QRpos()), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and leftnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.rightnull","page":"Tensors","title":"TensorKit.rightnull","text":"rightnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple;\n alg::OrthogonalFactorizationAlgorithm = LQ(),\n atol::Real = 0.0,\n rtol::Real = eps(real(float(one(scalartype(t)))))*iszero(atol)) -> N\n\nCreate orthonormal basis for the orthogonal complement of the support of the indices in rightind, such that permute(t, (leftind, rightind))*N' = 0.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using rightnull!(t, alg = LQpos()).\n\nDifferent algorithms are available, namely LQ() (or equivalently, LQpos), SVD() and SDD(). The first assumes that the matrix is full rank and requires iszero(atol) and iszero(rtol). With SVD() and SDD(), rightnull will use the corresponding singular value decomposition, and one can specify an absolute or relative tolerance for which singular values are to be considered zero, where max(atol, norm(t)*rtol) is used as upper bound.\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and rightnull(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.tsvd","page":"Tensors","title":"TensorKit.tsvd","text":"tsvd(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple;\n trunc::TruncationScheme = notrunc(), p::Real = 2, alg::Union{SVD, SDD} = SDD())\n -> U, S, V, ϵ\n\nCompute the (possibly truncated) singular value decomposition such that norm(permute(t, (leftind, rightind)) - U * S * V) ≈ ϵ, where ϵ thus represents the truncation error.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using tsvd!(t, trunc = notrunc(), p = 2).\n\nA truncation parameter trunc can be specified for the new internal dimension, in which case a truncated singular value decomposition will be computed. Choices are:\n\nnotrunc(): no truncation (default);\ntruncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η;\ntruncdim(χ::Int): truncates such that the equivalent total dimension of the internal vector space is no larger than χ;\ntruncspace(V): truncates such that the dimension of the internal vector space is smaller than that of V in any sector.\ntruncbelow(η::Real): truncates such that every singular value is larger then η ;\n\nTruncation options can also be combined using &, i.e. truncbelow(η) & truncdim(χ) will choose the truncation space such that every singular value is larger than η, and the equivalent total dimension of the internal vector space is no larger than χ.\n\nThe method tsvd also returns the truncation error ϵ, computed as the p norm of the singular values that were truncated.\n\nTHe keyword alg can be equal to SVD() or SDD(), corresponding to the underlying LAPACK algorithm that computes the decomposition (_gesvd or _gesdd).\n\nOrthogonality requires InnerProductStyle(t) <: HasInnerProduct, and tsvd(!) is currently only implemented for InnerProductStyle(t) === EuclideanInnerProduct().\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.eigh","page":"Tensors","title":"TensorKit.eigh","text":"eigh(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eigh assumes that the linear map is hermitian and D and V tensors with the same scalartype as t. See eig and eigen for non-hermitian tensors. Hermiticity requires that the tensor acts on inner product spaces, and the current implementation requires InnerProductStyle(t) === EuclideanInnerProduct().\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigh!(t). Note that the permuted tensor on which eigh! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nSee also eigen and eig.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#TensorKit.eig","page":"Tensors","title":"TensorKit.eig","text":"eig(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind. The function eig assumes that the linear map is not hermitian and returns type stable complex valued D and V tensors for both real and complex valued t. See eigh for hermitian linear maps\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eig!(t). Note that the permuted tensor on which eig! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nAccepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.\n\nSee also eigen and eigh.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.eigen","page":"Tensors","title":"LinearAlgebra.eigen","text":"eigen(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V\n\nCompute eigenvalue factorization of tensor t as linear map from rightind to leftind.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using eigen!(t). Note that the permuted tensor on which eigen! is called should have equal domain and codomain, as otherwise the eigenvalue decomposition is meaningless and cannot satisfy\n\npermute(t, (leftind, rightind)) * V = V * D\n\nAccepts the same keyword arguments scale and permute as eigen of dense matrices. See the corresponding documentation for more information.\n\nSee also eig and eigh\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/#LinearAlgebra.isposdef","page":"Tensors","title":"LinearAlgebra.isposdef","text":"isposdef(t::AbstractTensor, (leftind, rightind)::Index2Tuple) -> ::Bool\n\nTest whether a tensor t is positive definite as linear map from rightind to leftind.\n\nIf leftind and rightind are not specified, the current partition of left and right indices of t is used. In that case, less memory is allocated if one allows the data in t to be destroyed/overwritten, by using isposdef!(t). Note that the permuted tensor on which isposdef! is called should have equal domain and codomain, as otherwise it is meaningless.\n\n\n\n\n\n","category":"function"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"The corresponding destructive methods have an exclamation mark at the end of their name, and only accept the TensorMap object as well as the method-specific algorithm and keyword arguments.","category":"page"},{"location":"lib/tensors/","page":"Tensors","title":"Tensors","text":"TODO: document svd truncation types","category":"page"},{"location":"man/tensors/#s_tensors","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"using TensorKit\nusing LinearAlgebra","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This last page explains how to create and manipulate tensors in TensorKit.jl. As this is probably the most important part of the manual, we will also focus more strongly on the usage and interface, and less so on the underlying implementation. The only aspect of the implementation that we will address is the storage of the tensor data, as this is important to know how to create and initialize a tensor, but will in fact also shed light on how some of the methods work.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"As mentioned, all tensors in TensorKit.jl are interpreted as linear maps (morphisms) from a domain (a ProductSpace{S,N₂}) to a codomain (another ProductSpace{S,N₁}), with the same S<:ElementarySpace that labels the type of spaces associated with the individual tensor indices. The overall type for all such tensor maps is AbstractTensorMap{S, N₁, N₂}. Note that we place information about the codomain before that of the domain. Indeed, we have already encountered the constructor for the concrete parametric type TensorMap in the form TensorMap(..., codomain, domain). This convention is opposite to the mathematical notation, e.g. mathrmHom(WV) or fWV, but originates from the fact that a normal matrix is also denoted as having size m × n or is constructed in Julia as Array(..., (m, n)), where the first integer m refers to the codomain being m- dimensional, and the seond integer n to the domain being n-dimensional. This also explains why we have consistently used the symbol W for spaces in the domain and V for spaces in the codomain. A tensor map t(W₁ W_N₂) (V₁ V_N₁) will be created in Julia as TensorMap(..., V1 ⊗ ... ⊗ VN₁, W1 ⊗ ... ⊗ WN2).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, the abstract type AbstractTensor{S,N} is just a synonym for AbstractTensorMap{S,N,0}, i.e. for tensor maps with an empty domain, which is equivalent to the unit of the monoidal category, or thus, the field of scalars 𝕜.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Currently, AbstractTensorMap has two subtypes. TensorMap provides the actual implementation, where the data of the tensor is stored in a DenseArray (more specifically a DenseMatrix as will be explained below). AdjointTensorMap is a simple wrapper type to denote the adjoint of an existing TensorMap object. In the future, additional types could be defined, to deal with sparse data, static data, diagonal data, etc...","category":"page"},{"location":"man/tensors/#ss_tensor_storage","page":"Tensors and the TensorMap type","title":"Storage of tensor data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Before discussion how to construct and initalize a TensorMap{S}, let us discuss what is meant by 'tensor data' and how it can efficiently and compactly be stored. Let us first discuss the case sectortype(S) == Trivial sector, i.e. the case of no symmetries. In that case the data of a tensor t = TensorMap(..., V1 ⊗ ... ⊗ VN₁, W1 ⊗ ... ⊗ WN2) can just be represented as a multidimensional array of size","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(dim(V1), dim(V2), …, dim(VN₁), dim(W1), …, dim(WN₂))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"which can also be reshaped into matrix of size","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(dim(V1)*dim(V2)*…*dim(VN₁), dim(W1)*dim(W2)*…*dim(WN₂))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and is really the matrix representation of the linear map that the tensor represents. In particular, given a second tensor t2 whose domain matches with the codomain of t, function composition amounts to multiplication of their corresponding data matrices. Similarly, tensor factorizations such as the singular value decomposition, which we discuss below, can act directly on this matrix representation.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"note: Note\nOne might wonder if it would not have been more natural to represent the tensor data as (dim(V1), dim(V2), …, dim(VN₁), dim(WN₂), …, dim(W1)) given how employing the duality naturally reverses the tensor product, as encountered with the interface of repartition for fusion trees. However, such a representation, when plainly reshaped to a matrix, would not have the above properties and would thus not constitute the matrix representation of the tensor in a compatible basis.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Now consider the case where sectortype(S) == I for some I which has FusionStyle(I) == UniqueFusion(), i.e. the representations of an Abelian group, e.g. I == Irrep[ℤ₂] or I == Irrep[U₁]. In this case, the tensor data is associated with sectors (a1, a2, …, aN₁) ∈ sectors(V1 ⊗ V2 ⊗ … ⊗ VN₁) and (b1, …, bN₂) ∈ sectors(W1 ⊗ … ⊗ WN₂) such that they fuse to a same common charge, i.e. (c = first(⊗(a1, …, aN₁))) == first(⊗(b1, …, bN₂)). The data associated with this takes the form of a multidimensional array with size (dim(V1, a1), …, dim(VN₁, aN₁), dim(W1, b1), …, dim(WN₂, bN₂)), or equivalently, a matrix of with row size dim(V1, a1)*…*dim(VN₁, aN₁) == dim(codomain, (a1, …, aN₁)) and column size dim(W1, b1)*…*dim(WN₂, aN₂) == dim(domain, (b1, …, bN₂)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"However, there are multiple combinations of (a1, …, aN₁) giving rise to the same c, and so there is data associated with all of these, as well as all possible combinations of (b1, …, bN₂). Stacking all matrices for different (a1,…) and a fixed value of (b1,…) underneath each other, and for fixed value of (a1,…) and different values of (b1,…) next to each other, gives rise to a larger block matrix of all data associated with the central sector c. The size of this matrix is exactly (blockdim(codomain, c), blockdim(domain, c)) and these matrices are exactly the diagonal blocks whose existence is guaranteed by Schur's lemma, and which are labeled by the coupled sector c. Indeed, if we would represent the tensor map t as a matrix without explicitly using the symmetries, we could reorder the rows and columns to group data corresponding to sectors that fuse to the same c, and the resulting block diagonal representation would emerge. This basis transform is thus a permutation, which is a unitary operation, that will cancel or go through trivially for linear algebra operations such as composing tensor maps (matrix multiplication) or tensor factorizations such as a singular value decomposition. For such linear algebra operations, we can thus directly act on these large matrices, which correspond to the diagonal blocks that emerge after a basis transform, provided that the partition of the tensor indices in domain and codomain of the tensor are in line with our needs. For example, composing two tensor maps amounts to multiplying the matrices corresponding to the same c (provided that its subblocks labeled by the different combinations of sectors are ordered in the same way, which we guarantee by associating a canonical order with sectors). Henceforth, we refer to the blocks of a tensor map as those diagonal blocks, the existence of which is provided by Schur's lemma and which are labeled by the coupled sectors c. We directly store these blocks as DenseMatrix and gather them as values in a dictionary, together with the corresponding coupled sector c as key. For a given tensor t, we can access a specific block as block(t, c), whereas blocks(t) yields an iterator over pairs c=>block(t,c).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The subblocks corresponding to a particular combination of sectors then correspond to a particular view for some range of the rows and some range of the colums, i.e. view(block(t, c), m₁:m₂, n₁:n₂) where the ranges m₁:m₂ associated with (a1, …, aN₁) and n₁:n₂ associated with (b₁, …, bN₂) are stored within the fields of the instance t of type TensorMap. This view can then lazily be reshaped to a multidimensional array, for which we rely on the package Strided.jl. Indeed, the data in this view is not contiguous, because the stride between the different columns is larger than the length of the columns. Nonetheless, this does not pose a problem and even as multidimensional array there is still a definite stride associated with each dimension.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When FusionStyle(I) isa MultipleFusion, things become slightly more complicated. Not only do (a1, …, aN₁) give rise to different coupled sectors c, there can be multiply ways in which they fuse to c. These different possibilities are enumerated by the iterator fusiontrees((a1, …, aN₁), c) and fusiontrees((b1, …, bN₂), c), and with each of those, there is tensor data that takes the form of a multidimensional array, or, after reshaping, a matrix of size (dim(codomain, (a1, …, aN₁)), dim(domain, (b1, …, bN₂)))). Again, we can stack all such matrices with the same value of f₁ ∈ fusiontrees((a1, …, aN₁), c) horizontally (as they all have the same number of rows), and with the same value of f₂ ∈ fusiontrees((b1, …, bN₂), c) vertically (as they have the same number of columns). What emerges is a large matrix of size (blockdim(codomain, c), blockdim(domain, c)) containing all the tensor data associated with the coupled sector c, where blockdim(P, c) = sum(dim(P, s)*length(fusiontrees(s, c)) for s in sectors(P)) for some instance P of ProductSpace. The tensor implementation does not distinguish between abelian or non-abelian sectors and still stores these matrices as a DenseMatrix, accessible via block(t, c).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"At first sight, it might now be less clear what the relevance of this block is in relation to the full matrix representation of the tensor map, where the symmetry is not exploited. The essential interpretation is still the same. Schur's lemma now tells that there is a unitary basis transform which makes this matrix representation block diagonal, more specifically, of the form _c B_c 𝟙_c, where B_c denotes block(t,c) and 𝟙_c is an identity matrix of size (dim(c), dim(c)). The reason for this extra identity is that the group representation is recoupled to act as _c 𝟙 u_c(g) for all g mathsfI, with u_c(g) the matrix representation of group element g according to the irrep c. In the abelian case, dim(c) == 1, i.e. all irreducible representations are one-dimensional and Schur's lemma only dictates that all off-diagonal blocks are zero. However, in this case the basis transform to the block diagonal representation is not simply a permutation matrix, but a more general unitary matrix composed of the different fusion trees. Indeed, let us denote the fusion trees f₁ ∈ fusiontrees((a1, …, aN₁), c) as X^a_1 a_N₁_cα where α = (e_1 e_N_1-2 μ₁ μ_N_1-1) is a collective label for the internal sectors e and the vertex degeneracy labels μ of a generic fusion tree, as discussed in the corresponding section. The tensor is then represented as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor storage)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In this diagram, we have indicated how the tensor map can be rewritten in terms of a block diagonal matrix with a unitary matrix on its left and another unitary matrix (if domain and codomain are different) on its right. So the left and right matrices should actually have been drawn as squares. They represent the unitary basis transform. In this picture, red and white regions are zero. The center matrix is most easy to interpret. It is the block diagonal matrix _c B_c 𝟙_c with diagonal blocks labeled by the coupled charge c, in this case it takes two values. Every single small square in between the dotted or dashed lines has size d_c d_c and corresponds to a single element of B_c, tensored with the identity mathrmid_c. Instead of B_c, a more accurate labelling is t^c_(a_1 a_N₁)α (b_1 b_N₂)β where α labels different fusion trees from (a_1 a_N₁) to c. The dashed horizontal lines indicate regions corresponding to different fusion (actually splitting) trees, either because of different sectors (a_1 a_N₁) or different labels α within the same sector. Similarly, the dashed vertical lines define the border between regions of different fusion trees from the domain to c, either because of different sectors (b_1 b_N₂) or a different label β.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To understand this better, we need to understand the basis transform, e.g. on the left (codomain) side. In more detail, it is given by","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor unitary)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Indeed, remembering that V_i = _a_i R_a_i ℂ^n_a_i with R_a_i the representation space on which irrep a_i acts (with dimension mathrmdim(a_i)), we find V_1 V_N_1 = _a_1 a_N₁ (R_a_1 R_a_N_1) ℂ^n_a_1 n_a_N_1. In the diagram above, the wiggly lines correspond to the direct sum over the different sectors (a_1 a_N₁), there depicted taking three possible values (a), (a) and (a). The tensor product (R_a_1 R_a_N_1) ℂ^n_a_1 n_a_N_1 is depicted as (R_a_1 R_a_N_1)^ n_a_1 n_a_N_1, i.e. as a direct sum of the spaces R_(a) = (R_a_1 R_a_N_1) according to the dotted horizontal lines, which repeat n_(a) = n_a_1 n_a_N_1 times. In this particular example, n_(a)=2, n_(a)=3 and n_(a)=5. The thick vertical line represents the separation between the two different coupled sectors, denoted as c and c. Dashed vertical lines represent different ways of reaching the coupled sector, corresponding to different α. In this example, the first sector (a) has one fusion tree to c, labeled by cα, and two fusion trees to c, labeled by cα and cα. The second sector has only a fusion tree to c, labeled by cα. The third sector only has a fusion tree to c, labeld by c α. Finally then, because the fusion trees do not act on the spaces ℂ^n_a_1 n_a_N_1, the dotted lines which represent the different n_(a) = n_a_1 n_a_N_1 dimensions are also drawn vertically. In particular, for a given sector (a) and a specific fusion tree X^(a)_cα R_(a)R_c, the action is X^(a)_cα 𝟙_n_(a), which corresponds to the diagonal green blocks in this drawing where the same matrix X^(a)_cα (the fusion tree) is repeated along the diagonal. Note that the fusion tree is not a vector or single column, but a matrix with number of rows equal to mathrmdim(R_(aldots)) = d_a_1 d_a_2 d_a_N_1 and number of columns equal to d_c. A similar interpretation can be given to the basis transform on the right, by taking its adjoint. In this particular example, it has two different combinations of sectors (b) and (b), where both have a single fusion tree to c as well as to c, and n_(b)=2, n_(b)=3.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that we never explicitly store or act with the basis transforms on the left and the right. For composing tensor maps (i.e. multiplying them), these basis transforms just cancel, whereas for tensor factorizations they just go through trivially. They transform non-trivially when reshuffling the tensor indices, both within or in between the domain and codomain. For this, however, we can completely rely on the manipulations of fusion trees to implicitly compute the effect of the basis transform and construct the new blocks B_c that result with respect to the new basis.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, as before, we only store the diagonal blocks B_c of size (blockdim(codomain(t), c), blockdim(domain(t), c)) as a DenseMatrix, accessible via block(t, c). Within this matrix, there are regions of the form view(block(t, c), m₁:m₂, n₁:n₂) that correspond to the data t^c_(a_1 a_N₁)α (b_1 b_N₂)β associated with a pair of fusion trees X^(a_1 a_N₁)_cα and X^(b_1 b_N₂)_cβ, henceforth again denoted as f₁ and f₂, with f₁.coupled == f₂.coupled == c. The ranges where this subblock is living are managed within the tensor implementation, and these subblocks can be accessed via t[f₁,f₂], and is returned as a StridedArray of size n_a_1 n_a_2 n_a_N_1 n_b_1 n_b_N₂, or in code, (dim(V1, a1), dim(V2, a2), …, dim(VN₁, aN₁), dim(W1, b1), …, dim(WN₂, bN₂)). While the implementation does not distinguish between FusionStyle isa UniqueFusion or FusionStyle isa MultipleFusion, in the former case the fusion tree is completely characterized by the uncoupled sectors, and so the subblocks can also be accessed as t[(a1, …, aN₁, b1, …, bN₂)]. When there is no symmetry at all, i.e. sectortype(t) == Trivial, t[] returns the raw tensor data as a StridedArray of size (dim(V1), …, dim(VN₁), dim(W1), …, dim(WN₂)), whereas block(t, Trivial()) returns the same data as a DenseMatrix of size (dim(V1) * … * dim(VN₁), dim(W1) * … * dim(WN₂)).","category":"page"},{"location":"man/tensors/#ss_tensor_construction","page":"Tensors and the TensorMap type","title":"Constructing tensor maps and accessing tensor data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Having learned how a tensor is represented and stored, we can now discuss how to create tensors and tensor maps. From hereon, we focus purely on the interface rather than the implementation.","category":"page"},{"location":"man/tensors/#Random-and-uninitialized-tensor-maps","page":"Tensors and the TensorMap type","title":"Random and uninitialized tensor maps","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The most convenient set of constructors are those that construct tensors or tensor maps with random or uninitialized data. They take the form","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"f(codomain, domain = one(codomain))\nf(eltype::Type{<:Number}, codomain, domain = one(codomain))\nTensorMap(undef, codomain, domain = one(codomain))\nTensorMap(undef, eltype::Type{<:Number}, codomain, domain = one(codomain))\nTensor(undef, codomain)\nTensor(undef, eltype::Type{<:Number}, codomain)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, f is any of the typical functions from Base that normally create arrays, namely zeros, ones, rand, randn and Random.randexp. Remember that one(codomain) is the empty ProductSpace{S,0}(). The third and fourth calling syntax use the UndefInitializer from Julia Base and generates a TensorMap with unitialized data, which can thus contain NaNs.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In all of these constructors, the last two arguments can be replaced by domain→codomain or codomain←domain, where the arrows are obtained as \\rightarrow+TAB and \\leftarrow+TAB and create a HomSpace as explained in the section on Spaces of morphisms. Some examples are perhaps in order","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t1 = randn(ℂ^2 ⊗ ℂ^3, ℂ^2)\nt2 = zeros(Float32, ℂ^2 ⊗ ℂ^3 ← ℂ^2)\nt3 = TensorMap(undef, ℂ^2 → ℂ^2 ⊗ ℂ^3)\ndomain(t1) == domain(t2) == domain(t3)\ncodomain(t1) == codomain(t2) == codomain(t3)\ndisp(x) = show(IOContext(Core.stdout, :compact=>false), \"text/plain\", trunc.(x; digits = 3));\nt1[] |> disp\nblock(t1, Trivial()) |> disp\nreshape(t1[], dim(codomain(t1)), dim(domain(t1))) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, all constructors can also be replaced by Tensor(..., codomain), in which case the domain is assumed to be the empty ProductSpace{S,0}(), which can easily be obtained as one(codomain). Indeed, the empty product space is the unit object of the monoidal category, equivalent to the field of scalars 𝕜, and thus the multiplicative identity (especially since * also acts as tensor product on vector spaces).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The matrices created by f are the matrices B_c discussed above, i.e. those returned by block(t, c). Only numerical matrices of type DenseMatrix are accepted, which in practice just means Julia's intrinsic Matrix{T} for some T<:Number. In the future, we will add support for CuMatrix from CuArrays.jl to harness GPU computing power, and maybe SharedArray from the Julia's SharedArrays standard library.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Support for static or sparse data is currently unavailable, and if it would be implemented, it would lead to new subtypes of AbstractTensorMap which are distinct from TensorMap. Future implementations of e.g. SparseTensorMap or StaticTensorMap could be useful. Furthermore, there could be specific implementations for tensors whose blocks are Diagonal.","category":"page"},{"location":"man/tensors/#Tensor-maps-from-existing-data","page":"Tensors and the TensorMap type","title":"Tensor maps from existing data","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To create a TensorMap with existing data, one can use the aforementioned form but with the function f replaced with the actual data, i.e. TensorMap(data, codomain, domain) or any of its equivalents.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, data can be of two types. It can be a dictionary (any Associative subtype) which has blocksectors c of type sectortype(codomain) as keys, and the corresponding matrix blocks as value, i.e. data[c] is some DenseMatrix of size (blockdim(codomain, c), blockdim(domain, c)). This is the form of how the data is stored within the TensorMap objects.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For those space types for which a TensorMap can be converted to a plain multidimensional array, the data can also be a general DenseArray, either of rank N₁+N₂ and with matching size (dims(codomain)..., dims(domain)...), or just as a DenseMatrix with size (dim(codomain), dim(domain)). This is true in particular if the sector type is Trivial, e.g. for CartesianSpace or ComplexSpace. Then the data array is just reshaped into matrix form and referred to as such in the resulting TensorMap instance. When spacetype is GradedSpace, the TensorMap constructor will try to reconstruct the tensor data such that the resulting tensor t satisfies data == convert(Array, t). This might not be possible, if the data does not respect the symmetry structure. This procedure can be sketched using a simple physical example, namely the SWAP gate on two qubits,","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"beginalign*\nmathrmSWAP mathbbC^2 otimes mathbbC^2 to mathbbC^2 otimes mathbbC^2\nirangle otimes jrangle mapsto jrangle otimes irangle\nendalign*","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This operator can be rewritten in terms of the familiar Heisenberg exchange interaction vecS_i cdot vecS_j as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"mathrmSWAP = 2 vecS_i cdot vecS_j + frac12 𝟙","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where vecS = (S^x S^y S^z) and the spin-1/2 generators of SU₂ S^k are defined defined in terms of the 2 times 2 Pauli matrices sigma^k as S^k = frac12sigma^k. The SWAP gate can be realized as a rank-4 TensorMap in the following way:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"# encode the matrix elements of the swap gate into a rank-4 array, where the first two\n# indices correspond to the codomain and the last two indices correspond to the domain\ndata = zeros(2,2,2,2)\n# the swap gate then maps the last two indices on the first two in reversed order\ndata[1,1,1,1] = data[2,2,2,2] = data[1,2,2,1] = data[2,1,1,2] = 1\nV1 = ℂ^2 # generic qubit hilbert space\nt1 = TensorMap(data, V1 ⊗ V1, V1 ⊗ V1)\nV2 = SU2Space(1/2=>1) # hilbert space of an actual spin-1/2 particle, respecting symmetry\nt2 = TensorMap(data, V2 ⊗ V2, V2 ⊗ V2)\nV3 = U1Space(1/2=>1,-1/2=>1) # restricted space that only uses the `σ_z` rotation symmetry\nt3 = TensorMap(data, V3 ⊗ V3, V3 ⊗ V3)\nfor (c,b) in blocks(t3)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, we recognize that the exchange interaction has eigenvalue -1 in the coupled spin zero sector (SU2Irrep(0)), and eigenvalue +1 in the coupled spin 1 sector (SU2Irrep(1)). Using Irrep[U₁] instead, we observe that both coupled charge U1Irrep(+1) and U1Irrep(-1) have eigenvalue +1. The coupled charge U1Irrep(0) sector is two-dimensional, and has an eigenvalue +1 and an eigenvalue -1.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To construct the proper data in more complicated cases, one has to know where to find each sector in the range 1:dim(V) of every index i with associated space V, as well as the internal structure of the representation space when the corresponding sector c has dim(c)>1, i.e. in the case of FusionStyle(c) isa MultipleFusion. Currently, the only non- abelian sectors are Irrep[SU₂] and Irrep[CU₁], for which the internal structure is the natural one.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are some tools available to facilitate finding the proper range of sector c in space V, namely axes(V, c). This also works on a ProductSpace, with a tuple of sectors. An example","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V = SU2Space(0=>3, 1=>2, 2=>1)\nP = V ⊗ V ⊗ V\naxes(P, (SU2Irrep(1), SU2Irrep(0), SU2Irrep(2)))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the length of the range is the degeneracy dimension of that sector, times the dimension of the internal representation space, i.e. the quantum dimension of that sector.","category":"page"},{"location":"man/tensors/#Assigning-block-data-after-initialization","page":"Tensors and the TensorMap type","title":"Assigning block data after initialization","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In order to avoid having to know the internal structure of each representation space to properly construct the full data array, it is often simpler to assign the block data directly after initializing an all zero TensorMap with the correct spaces. While this may seem more difficult at first sight since it requires knowing the exact entries associated to each valid combination of domain uncoupled sectors, coupled sector and codomain uncoupled sectors, this is often a far more natural procedure in practice.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A first option is to directly set the full matrix block for each coupled sector in the TensorMap. For the example with U₁ symmetry, this can be done as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t4 = zeros(V3 ⊗ V3, V3 ⊗ V3);\nblock(t4, U1Irrep(0)) .= [1 0; 0 1];\nblock(t4, U1Irrep(1)) .= [1;;];\nblock(t4, U1Irrep(-1)) .= [1;;];\nfor (c, b) in blocks(t4)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"While this indeed does not require considering the internal structure of the representation spaces, it still requires knowing the precise row and column indices corresponding to each set of uncoupled sectors in the codmain and domain respectively to correctly assign the nonzero entries in each block.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Perhaps the most natural way of constructing a particular TensorMap is to directly assign the data slices for each splitting - fusion tree pair using the fusiontrees(::TensorMap) method. This returns an iterator over all tuples (f₁, f₂) of splitting - fusion tree pairs corresponding to all ways in which the set of domain uncoupled sectors can fuse to a coupled sector and split back into the set of codomain uncoupled sectors. By directly setting the corresponding data slice t[f₁, f₂] of size (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...), we can construct all the block data without worrying about the internal ordering of row and column indices in each block. In addition, the corresponding value of each fusion tree slice is often directly informed by the object we are trying to construct in the first place. For example, in order to construct the Heisenberg exchange interaction on two spin-1/2 particles i and j as an SU₂ symmetric TensorMap, we can make use of the observation that","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"vecS_i cdot vecS_j = frac12 left( left( vecS_i cdot vecS_j right)^2 - vecS_i^2 - vecS_j^2 right)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Recalling some basic group theory, we know that the quadratic Casimir of SU₂, vecS^2, has a well-defined eigenvalue j(j+1) on every irrep of spin j. From the above expressions, we can therefore directly read off the eigenvalues of the SWAP gate in terms of this Casimir eigenvalue on the domain uncoupled sectors and the coupled sector. This gives us exactly the prescription we need to assign the data slice corresponding to each splitting - fusion tree pair:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"C(s::SU2Irrep) = s.j * (s.j + 1)\nt5 = zeros(V2 ⊗ V2, V2 ⊗ V2);\nfor (f₁, f₂) in fusiontrees(t5)\n t5[f₁, f₂] .= C(f₂.coupled) - C(f₂.uncoupled[1]) - C(f₂.uncoupled[2]) + 1/2\nend\nfor (c, b) in blocks(t5)\n println(\"Data for block $c :\")\n disp(b)\n println()\nend","category":"page"},{"location":"man/tensors/#Constructing-similar-tensors","page":"Tensors and the TensorMap type","title":"Constructing similar tensors","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A third way to construct a TensorMap instance is to use Base.similar, i.e.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"similar(t [, T::Type{<:Number}, codomain, domain])","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where T is a possibly different eltype for the tensor data, and codomain and domain optionally define a new codomain and domain for the resulting tensor. By default, these values just take the value from the input tensor t. The result will be a new TensorMap instance, with undef data, but whose data is stored in the same subtype of DenseMatrix (e.g. Matrix or CuMatrix or ...) as t. In particular, this uses the methods storagetype(t) and TensorKit.similarstoragetype(t, T).","category":"page"},{"location":"man/tensors/#Special-purpose-constructors","page":"Tensors and the TensorMap type","title":"Special purpose constructors","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are methods zero, one, id, isomorphism, unitary and isometry to create specific new tensors. Tensor maps behave as vectors and can be added (if they have the same domain and codomain); zero(t) is the additive identity, i.e. a TensorMap instance where all entries are zero. For a t::TensorMap with domain(t) == codomain(t), i.e. an endomorphism, one(t) creates the identity tensor, i.e. the identity under composition. As discussed in the section on linear algebra operations, we denote composition of tensor maps with the mutliplication operator *, such that one(t) is the multiplicative identity. Similarly, it can be created as id(V) with V the relevant vector space, e.g. one(t) == id(domain(t)). The identity tensor is currently represented with dense data, and one can use id(A::Type{<:DenseMatrix}, V) to specify the type of DenseMatrix (and its eltype), e.g. A = Matrix{Float64}. Finally, it often occurs that we want to construct a specific isomorphism between two spaces that are isomorphic but not equal, and for which there is no canonical choice. Hereto, one can use the method u = isomorphism([A::Type{<:DenseMatrix}, ] codomain, domain), which will explicitly check that the domain and codomain are isomorphic, and return an error otherwise. Again, an optional first argument can be given to specify the specific type of DenseMatrix that is currently used to store the rather trivial data of this tensor. If InnerProductStyle(u) <: EuclideanProduct, the same result can be obtained with the method u = unitary([A::Type{<:DenseMatrix}, ] codomain, domain). Note that reversing the domain and codomain yields the inverse morphism, which in the case of EuclideanProduct coincides with the adjoint morphism, i.e. isomorphism(A, domain, codomain) == adjoint(u) == inv(u), where inv and adjoint will be further discussed below. Finally, if two spaces V1 and V2 are such that V2 can be embedded in V1, i.e. there exists an inclusion with a left inverse, and furthermore they represent tensor products of some ElementarySpace with EuclideanProduct, the function w = isometry([A::Type{<:DenseMatrix}, ], V1, V2) creates one specific isometric embedding, such that adjoint(w) * w == id(V2) and w * adjoint(w) is some hermitian idempotent (a.k.a. orthogonal projector) acting on V1. An error will be thrown if such a map cannot be constructed for the given domain and codomain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's conclude this section with some examples with GradedSpace.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = ℤ₂Space(0=>3,1=>2)\nV2 = ℤ₂Space(0=>2,1=>1)\n# First a `TensorMap{ℤ₂Space, 1, 1}`\nm = randn(V1, V2)\nconvert(Array, m) |> disp\n# compare with:\nblock(m, Irrep[ℤ₂](0)) |> disp\nblock(m, Irrep[ℤ₂](1)) |> disp\n# Now a `TensorMap{ℤ₂Space, 2, 2}`\nt = randn(V1 ⊗ V1, V2 ⊗ V2')\n(array = convert(Array, t)) |> disp\nd1 = dim(codomain(t))\nd2 = dim(domain(t))\n(matrix = reshape(array, d1, d2)) |> disp\n(u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp\n(v = reshape(convert(Array, unitary(domain(t), fuse(domain(t)))), d2, d2)) |> disp\nu'*u ≈ I ≈ v'*v\n(u'*matrix*v) |> disp\n# compare with:\nblock(t, Z2Irrep(0)) |> disp\nblock(t, Z2Irrep(1)) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Here, we illustrated some additional concepts. Firstly, note that we convert a TensorMap to an Array. This only works when sectortype(t) supports fusiontensor, and in particular when BraidingStyle(sectortype(t)) == Bosonic(), e.g. the case of trivial tensors (the category mathbfVect) and group representations (the category mathbfRep_mathsfG, which can be interpreted as a subcategory of mathbfVect). Here, we are in this case with mathsfG = ℤ₂. For a TensorMap{S,1,1}, the blocks directly correspond to the diagonal blocks in the block diagonal structure of its representation as an Array, there is no basis transform in between. This is no longer the case for TensorMap{S,N₁,N₂} with different values of N₁ and N₂. Here, we use the operation fuse(V), which creates an ElementarySpace which is isomorphic to a given space V (of type ProductSpace or ElementarySpace). The specific map between those two spaces constructed using the specific method unitary implements precisely the basis change from the product basis to the coupled basis. In this case, for a group G with FusionStyle(Irrep[G]) isa UniqueFusion, it is a permutation matrix. Specifically choosing V equal to the codomain and domain of t, we can construct the explicit basis transforms that bring t into block diagonal form.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's repeat the same exercise for I = Irrep[SU₂], which has FusionStyle(I) isa MultipleFusion.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = SU₂Space(0=>2,1=>1)\nV2 = SU₂Space(0=>1,1=>1)\n# First a `TensorMap{SU₂Space, 1, 1}`\nm = randn(V1, V2)\nconvert(Array, m) |> disp\n# compare with:\nblock(m, Irrep[SU₂](0)) |> disp\nblock(m, Irrep[SU₂](1)) |> disp\n# Now a `TensorMap{SU₂Space, 2, 2}`\nt = randn(V1 ⊗ V1, V2 ⊗ V2')\n(array = convert(Array, t)) |> disp\nd1 = dim(codomain(t))\nd2 = dim(domain(t))\n(matrix = reshape(array, d1, d2)) |> disp\n(u = reshape(convert(Array, unitary(codomain(t), fuse(codomain(t)))), d1, d1)) |> disp\n(v = reshape(convert(Array, unitary(domain(t), fuse(domain(t)))), d2, d2)) |> disp\nu'*u ≈ I ≈ v'*v\n(u'*matrix*v) |> disp\n# compare with:\nblock(t, SU2Irrep(0)) |> disp\nblock(t, SU2Irrep(1)) |> disp\nblock(t, SU2Irrep(2)) |> disp","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the basis transforms u and v are no longer permutation matrices, but are still unitary. Furthermore, note that they render the tensor block diagonal, but that now every element of the diagonal blocks labeled by c comes itself in a tensor product with an identity matrix of size dim(c), i.e. dim(SU2Irrep(1)) = 3 and dim(SU2Irrep(2)) = 5.","category":"page"},{"location":"man/tensors/#ss_tensor_properties","page":"Tensors and the TensorMap type","title":"Tensor properties","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Given a t::AbstractTensorMap{S,N₁,N₂}, there are various methods to query its properties. The most important are clearly codomain(t) and domain(t). For t::AbstractTensor{S,N}, i.e. t::AbstractTensorMap{S,N,0}, we can use space(t) as synonym for codomain(t). However, for a general AbstractTensorMap this has no meaning. However, we can query space(t, i), the space associated with the ith index. For i ∈ 1:N₁, this corresponds to codomain(t, i) = codomain(t)[i]. For j = i-N₁ ∈ (1:N₂), this corresponds to dual(domain(t, j)) = dual(domain(t)[j]).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The total number of indices, i.e. N₁+N₂, is given by numind(t), with N₁ == numout(t) and N₂ == numin(t), the number of outgoing and incoming indices. There are also the unexported methods TensorKit.codomainind(t) and TensorKit.domainind(t) which return the tuples (1, 2, …, N₁) and (N₁+1, …, N₁+N₂), and are useful for internal purposes. The type parameter S<:ElementarySpace can be obtained as spacetype(t); the corresponding sector can directly obtained as sectortype(t) and is Trivial when S != GradedSpace. The underlying field scalars of S can also directly be obtained as field(t). This is different from eltype(t), which returns the type of Number in the tensor data, i.e. the type parameter T in the (subtype of) DenseMatrix{T} in which the matrix blocks are stored. Note that during construction, a (one-time) warning is printed if !(T ⊂ field(S)). The specific DenseMatrix{T} subtype in which the tensor data is stored is obtained as storagetype(t). Each of the methods numind, numout, numin, TensorKit.codomainind, TensorKit.domainind, spacetype, sectortype, field, eltype and storagetype work in the type domain as well, i.e. they are encoded in typeof(t).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are methods to probe the data, which we already encountered. blocksectors(t) returns an iterator over the different coupled sectors that can be obtained from fusing the uncoupled sectors available in the domain, but they must also be obtained from fusing the uncoupled sectors available in the codomain (i.e. it is the intersection of both blocksectors(codomain(t)) and blocksectors(domain(t))). For a specific sector c ∈ blocksectors(t), block(t, c) returns the corresponding data. Both are obtained together with blocks(t), which returns an iterator over the pairs c=>block(t, c). Furthermore, there is fusiontrees(t) which returns an iterator over splitting-fusion tree pairs (f₁,f₂), for which the corresponding data is given by t[f₁,f₂] (i.e. using Base.getindex).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Let's again illustrate these methods with an example, continuing with the tensor t from the previous example","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"typeof(t)\ncodomain(t)\ndomain(t)\nspace(t,1)\nspace(t,2)\nspace(t,3)\nspace(t,4)\nnumind(t)\nnumout(t)\nnumin(t)\nspacetype(t)\nsectortype(t)\nfield(t)\neltype(t)\nstoragetype(t)\nblocksectors(t)\nblocks(t)\nblock(t, first(blocksectors(t)))\nfusiontrees(t)\nf1, f2 = first(fusiontrees(t))\nt[f1,f2]","category":"page"},{"location":"man/tensors/#ss_tensor_readwrite","page":"Tensors and the TensorMap type","title":"Reading and writing tensors: Dict conversion","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are no custom or dedicated methods for reading, writing or storing TensorMaps, however, there is the possibility to convert a t::AbstractTensorMap into a Dict, simply as convert(Dict, t). The backward conversion convert(TensorMap, dict) will return a tensor that is equal to t, i.e. t == convert(TensorMap, convert(Dict, t)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This conversion relies on that the string represenation of objects such as VectorSpace, FusionTree or Sector should be such that it represents valid code to recreate the object. Hence, we store information about the domain and codomain of the tensor, and the sector associated with each data block, as a String obtained with repr. This provides the flexibility to still change the internal structure of such objects, without this breaking the ability to load older data files. The resulting dictionary can then be stored using any of the provided Julia packages such as JLD.jl, JLD2.jl, BSON.jl, JSON.jl, ...","category":"page"},{"location":"man/tensors/#ss_tensor_linalg","page":"Tensors and the TensorMap type","title":"Vector space and linear algebra operations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances t represent linear maps, i.e. homomorphisms in a 𝕜-linear category, just like matrices. To a large extent, they follow the interface of Matrix in Julia's LinearAlgebra standard library. Many methods from LinearAlgebra are (re)exported by TensorKit.jl, and can then us be used without using LinearAlgebra explicitly. In all of the following methods, the implementation acts directly on the underlying matrix blocks (typically using the same method) and never needs to perform any basis transforms.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In particular, AbstractTensorMap instances can be composed, provided the domain of the first object coincides with the codomain of the second. Composing tensor maps uses the regular multiplication symbol as in t = t1*t2, which is also used for matrix multiplication. TensorKit.jl also supports (and exports) the mutating method mul!(t, t1, t2). We can then also try to invert a tensor map using inv(t), though this can only exist if the domain and codomain are isomorphic, which can e.g. be checked as fuse(codomain(t)) == fuse(domain(t)). If the inverse is composed with another tensor t2, we can use the syntax t1\\t2 or t2/t1. However, this syntax also accepts instances t1 whose domain and codomain are not isomorphic, and then amounts to pinv(t1), the Moore-Penrose pseudoinverse. This, however, is only really justified as minimizing the least squares problem if InnerProductStyle(t) <: EuclideanProduct.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances behave themselves as vectors (i.e. they are 𝕜-linear) and so they can be multiplied by scalars and, if they live in the same space, i.e. have the same domain and codomain, they can be added to each other. There is also a zero(t), the additive identity, which produces a zero tensor with the same domain and codomain as t. In addition, TensorMap supports basic Julia methods such as fill! and copy!, as well as copy(t) to create a copy with independent data. Aside from basic + and * operations, TensorKit.jl reexports a number of efficient in-place methods from LinearAlgebra, such as axpy! (for y ← α * x + y), axpby! (for y ← α * x + β * y), lmul! and rmul! (for y ← α*y and y ← y*α, which is typically the same) and mul!, which can also be used for out-of-place scalar multiplication y ← α*x.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For t::AbstractTensorMap{S} where InnerProductStyle(S) <: EuclideanProduct, we can compute norm(t), and for two such instances, the inner product dot(t1, t2), provided t1 and t2 have the same domain and codomain. Furthermore, there is normalize(t) and normalize!(t) to return a scaled version of t with unit norm. These operations should also exist for InnerProductStyle(S) <: HasInnerProduct, but require an interface for defining a custom inner product in these spaces. Currently, there is no concrete subtype of HasInnerProduct that is not an EuclideanProduct. In particular, CartesianSpace, ComplexSpace and GradedSpace all have InnerProductStyle(V) <: EuclideanProduct.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"With tensors that have InnerProductStyle(t) <: EuclideanProduct there is associated an adjoint operation, given by adjoint(t) or simply t', such that domain(t') == codomain(t) and codomain(t') == domain(t). Note that for an instance t::TensorMap{S,N₁,N₂}, t' is simply stored in a wrapper called AdjointTensorMap{S,N₂,N₁}, which is another subtype of AbstractTensorMap. This should be mostly unvisible to the user, as all methods should work for this type as well. It can be hard to reason about the index order of t', i.e. index i of t appears in t' at index position j = TensorKit.adjointtensorindex(t, i), where the latter method is typically not necessary and hence unexported. There is also a plural TensorKit.adjointtensorindices to convert multiple indices at once. Note that, because the adjoint interchanges domain and codomain, we have space(t', j) == space(t, i)'.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"AbstractTensorMap instances can furthermore be tested for exact (t1 == t2) or approximate (t1 ≈ t2) equality, though the latter requires that norm can be computed.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When tensor map instances are endomorphisms, i.e. they have the same domain and codomain, there is a multiplicative identity which can be obtained as one(t) or one!(t), where the latter overwrites the contents of t. The multiplicative identity on a space V can also be obtained using id(A, V) as discussed above, such that for a general homomorphism t′, we have t′ == id(codomain(t′))*t′ == t′*id(domain(t′)). Returning to the case of endomorphisms t, we can compute the trace via tr(t) and exponentiate them using exp(t), or if the contents of t can be destroyed in the process, exp!(t). Furthermore, there are a number of tensor factorizations for both endomorphisms and general homomorphism that we discuss below.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, there are a number of operations that also belong in this paragraph because of their analogy to common matrix operations. The tensor product of two TensorMap instances t1 and t2 is obtained as t1 ⊗ t2 and results in a new TensorMap with codomain(t1⊗t2) = codomain(t1) ⊗ codomain(t2) and domain(t1⊗t2) = domain(t1) ⊗ domain(t2). If we have two TensorMap{S,N,1} instances t1 and t2 with the same codomain, we can combine them in a way that is analoguous to hcat, i.e. we stack them such that the new tensor catdomain(t1, t2) has also the same codomain, but has a domain which is domain(t1) ⊕ domain(t2). Similarly, if t1 and t2 are of type TensorMap{S,1,N} and have the same domain, the operation catcodomain(t1, t2) results in a new tensor with the same domain and a codomain given by codomain(t1) ⊕ codomain(t2), which is the analogy of vcat. Note that direct sum only makes sense between ElementarySpace objects, i.e. there is no way to give a tensor product meaning to a direct sum of tensor product spaces.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Time for some more examples:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"t == t + zero(t) == t*id(domain(t)) == id(codomain(t))*t\nt2 = randn(ComplexF64, codomain(t), domain(t));\ndot(t2, t)\ntr(t2'*t)\ndot(t2, t) ≈ dot(t', t2')\ndot(t2, t2)\nnorm(t2)^2\nt3 = copy!(similar(t, ComplexF64), t);\nt3 == t\nrmul!(t3, 0.8);\nt3 ≈ 0.8*t\naxpby!(0.5, t2, 1.3im, t3);\nt3 ≈ 0.5 * t2 + 0.8 * 1.3im * t\nt4 = randn(fuse(codomain(t)), codomain(t));\nt5 = TensorMap(undef, fuse(codomain(t)), domain(t));\nmul!(t5, t4, t) == t4*t\ninv(t4) * t4 ≈ id(codomain(t))\nt4 * inv(t4) ≈ id(fuse(codomain(t)))\nt4 \\ (t4 * t) ≈ t\nt6 = randn(ComplexF64, V1, codomain(t));\nnumout(t4) == numout(t6) == 1\nt7 = catcodomain(t4, t6);\nforeach(println, (codomain(t4), codomain(t6), codomain(t7)))\nnorm(t7) ≈ sqrt(norm(t4)^2 + norm(t6)^2)\nt8 = t4 ⊗ t6;\nforeach(println, (codomain(t4), codomain(t6), codomain(t8)))\nforeach(println, (domain(t4), domain(t6), domain(t8)))\nnorm(t8) ≈ norm(t4)*norm(t6)","category":"page"},{"location":"man/tensors/#Index-manipulations","page":"Tensors and the TensorMap type","title":"Index manipulations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In many cases, the bipartition of tensor indices (i.e. ElementarySpace instances) between the codomain and domain is not fixed throughout the different operations that need to be performed on that tensor map, i.e. we want to use the duality to move spaces from domain to codomain and vice versa. Furthermore, we want to use the braiding to reshuffle the order of the indices.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For this, we use an interface that is closely related to that for manipulating splitting- fusion tree pairs, namely braid and permute, with the interface","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"braid(t::AbstractTensorMap{S,N₁,N₂}, levels::NTuple{N₁+N₂,Int},\n p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"permute(t::AbstractTensorMap{S,N₁,N₂},\n p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int}; copy = false)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"both of which return an instance of AbstractTensorMap{S,N₁′,N₂′}.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In these methods, p1 and p2 specify which of the original tensor indices ranging from 1 to N₁+N₂ make up the new codomain (with N₁′ spaces) and new domain (with N₂′ spaces). Hence, (p1..., p2...) should be a valid permutation of 1:(N₁+N₂). Note that, throughout TensorKit.jl, permutations are always specified using tuples of Ints, for reasons of type stability. For braid, we also need to specify levels or depths for each of the indices of the original tensor, which determine whether indices will braid over or underneath each other (use the braiding or its inverse). We refer to the section on manipulating fusion trees for more details.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can use the simpler interface of permute, which does not require the argument levels. permute accepts a keyword argument copy. When copy == true, the result will be a tensor with newly allocated data that can independently be modified from that of the input tensor t. When copy takes the default value false, permute can try to return the result in a way that it shares its data with the input tensor t, though this is only possible in specific cases (e.g. when sectortype(S) == Trivial and (p1..., p2...) = (1:(N₁+N₂)...)).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Both braid and permute come in a version where the result is stored in an already existing tensor, i.e. braid!(tdst, tsrc, levels, p1, p2) and permute!(tdst, tsrc, p1, p2).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Another operation that belongs und index manipulations is taking the transpose of a tensor, i.e. LinearAlgebra.transpose(t) and LinearAlgebra.transpose!(tdst, tsrc), both of which are reexported by TensorKit.jl. Note that transpose(t) is not simply equal to reshuffling domain and codomain with braid(t, (1:(N₁+N₂)...), reverse(domainind(tsrc)), reverse(codomainind(tsrc)))). Indeed, the graphical representation (where we draw the codomain and domain as a single object), makes clear that this introduces an additional (inverse) twist, which is then compensated in the transpose implementation.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: transpose)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In categorical language, the reason for this extra twist is that we use the left coevaluation η, but the right evaluation tildeϵ, when repartitioning the indices between domain and codomain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"There are a number of other index related manipulations. We can apply a twist (or inverse twist) to one of the tensor map indices via twist(t, i; inv = false) or twist!(t, i; inv = false). Note that the latter method does not store the result in a new destination tensor, but just modifies the tensor t in place. Twisting several indices simultaneously can be obtained by using the defining property","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"θ_VW = τ_WV (θ_W θ_V) τ_VW = (θ_V θ_W) τ_WV τ_VW","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"but is currently not implemented explicitly.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For all sector types I with BraidingStyle(I) == Bosonic(), all twists are 1 and thus have no effect. Let us start with some examples, in which we illustrate that, albeit permute might act highly non-trivial on the fusion trees and on the corresponding data, after conversion to a regular Array (when possible), it just acts like permutedims","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"domain(t) → codomain(t)\nta = convert(Array, t);\nt′ = permute(t, (1,2,3,4));\ndomain(t′) → codomain(t′)\nconvert(Array, t′) ≈ ta\nt′′ = permute(t, (4,2,3),(1,));\ndomain(t′′) → codomain(t′′)\nconvert(Array, t′′) ≈ permutedims(ta, (4,2,3,1))\nm\ntranspose(m)\nconvert(Array, transpose(t)) ≈ permutedims(ta,(4,3,2,1))\ndot(t2, t) ≈ dot(transpose(t2), transpose(t))\ntranspose(transpose(t)) ≈ t\ntwist(t, 3) ≈ t\n# as twist acts trivially for\nBraidingStyle(sectortype(t))","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that transpose acts like one would expect on a TensorMap{S,1,1}. On a TensorMap{S,N₁,N₂}, because transpose replaces the codomain with the dual of the domain, which has its tensor product operation reversed, this in the end amounts in a complete reversal of all tensor indices when representing it as a plain mutli-dimensional Array. Also, note that we have not defined the conjugation of TensorMap instances. One definition that one could think of is conj(t) = adjoint(transpose(t)). However note that codomain(adjoint(tranpose(t))) == domain(transpose(t)) == dual(codomain(t)) and similarly domain(adjoint(tranpose(t))) == dual(domain(t)), where dual of a ProductSpace is composed of the dual of the ElementarySpace instances, in reverse order of tensor product. This might be very confusing, and as such we leave tensor conjugation undefined. However, note that we have a conjugation syntax within the context of tensor contractions.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"To show the effect of twist, we now consider a type of sector I for which BraidingStyle{I} != Bosonic(). In particular, we use FibonacciAnyon. We cannot convert the resulting TensorMap to an Array, so we have to rely on indirect tests to verify our results.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = GradedSpace{FibonacciAnyon}(:I=>3,:τ=>2)\nV2 = GradedSpace{FibonacciAnyon}(:I=>2,:τ=>1)\nm = TensorMap(randn, Float32, V1, V2)\ntranspose(m)\ntwist(braid(m, (1,2), (2,), (1,)), 1)\nt1 = randn(V1*V2', V2*V1);\nt2 = randn(ComplexF64, V1*V2', V2*V1);\ndot(t1, t2) ≈ dot(transpose(t1), transpose(t2))\ntranspose(transpose(t1)) ≈ t1","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A final operation that one might expect in this section is to fuse or join indices, and its inverse, to split a given index into two or more indices. For a plain tensor (i.e. with sectortype(t) == Trivial) amount to the equivalent of reshape on the multidimensional data. However, this represents only one possibility, as there is no canonically unique way to embed the tensor product of two spaces V₁ ⊗ V₂ in a new space V = fuse(V₁⊗V₂). Such a mapping can always be accompagnied by a basis transform. However, one particular choice is created by the function isomorphism, or for EuclideanProduct spaces, unitary. Hence, we can join or fuse two indices of a tensor by first constructing u = unitary(fuse(space(t, i) ⊗ space(t, j)), space(t, i) ⊗ space(t, j)) and then contracting this map with indices i and j of t, as explained in the section on contracting tensors. Note, however, that a typical algorithm is not expected to often need to fuse and split indices, as e.g. tensor factorizations can easily be applied without needing to reshape or fuse indices first, as explained in the next section.","category":"page"},{"location":"man/tensors/#ss_tensor_factorization","page":"Tensors and the TensorMap type","title":"Tensor factorizations","text":"","category":"section"},{"location":"man/tensors/#Eigenvalue-decomposition","page":"Tensors and the TensorMap type","title":"Eigenvalue decomposition","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"As tensors are linear maps, they have various kinds of factorizations. Endomorphism, i.e. tensor maps t with codomain(t) == domain(t), have an eigenvalue decomposition. For this, we overload both LinearAlgebra.eigen(t; kwargs...) and LinearAlgebra.eigen!(t; kwargs...), where the latter destroys t in the process. The keyword arguments are the same that are accepted by LinearAlgebra.eigen(!) for matrices. The result is returned as D, V = eigen(t), such that t*V ≈ V*D. For given t::TensorMap{S,N,N}, V is a TensorMap{S,N,1}, whose codomain corresponds to that of t, but whose domain is a single space S (or more correctly a ProductSpace{S,1}), that corresponds to fuse(codomain(t)). The eigenvalues are encoded in D, a TensorMap{S,1,1}, whose domain and codomain correspond to the domain of V. Indeed, we cannot reasonably associate a tensor product structure with the different eigenvalues. Note that D stores the eigenvalues on the diagonal of a (collection of) DenseMatrix instance(s), as there is currently no dedicated DiagonalTensorMap or diagonal storage support.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"We also define LinearAlgebra.ishermitian(t), which can only return true for instances of AbstractEuclideanTensorMap. In all other cases, as the inner product is not defined, there is no notion of hermiticity (i.e. we are not working in a †-category). For instances of EuclideanTensorMap, we also define and export the routines eigh and eigh!, which compute the eigenvalue decomposition under the guarantee (not checked) that the map is hermitian. Hence, eigenvalues will be real and V will be unitary with eltype(V) == eltype(t). We also define and export eig and eig!, which similarly assume that the TensorMap is not hermitian (hence this does not require EuclideanTensorMap), and always returns complex values eigenvalues and eigenvectors. Like for matrices, LinearAlgebra.eigen is type unstable and checks hermiticity at run-time, then falling back to either eig or eigh.","category":"page"},{"location":"man/tensors/#Orthogonal-factorizations","page":"Tensors and the TensorMap type","title":"Orthogonal factorizations","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Other factorizations that are provided by TensorKit.jl are orthogonal or unitary in nature, and thus always require a AbstractEuclideanTensorMap. However, they don't require equal domain and codomain. Let us first discuss the singular value decomposition, for which we define and export the methods tsvd and tsvd! (where as always, the latter destroys the input).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"U, Σ, Vʰ, ϵ = tsvd(t; trunc = notrunc(), p::Real = 2,\n alg::OrthogonalFactorizationAlgorithm = SDD())","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"This computes a (possibly truncated) singular value decomposition of t::TensorMap{S,N₁,N₂} (with InnerProductStyle(t)<:EuclideanProduct), such that norm(t - U*Σ*Vʰ) ≈ ϵ, where U::TensorMap{S,N₁,1}, S::TensorMap{S,1,1}, Vʰ::TensorMap{S,1,N₂} and ϵ::Real. U is an isometry, i.e. U'*U approximates the identity, whereas U*U' is an idempotent (squares to itself). The same holds for adjoint(Vʰ). The domain of U equals the domain and codomain of Σ and the codomain of Vʰ. In the case of trunc = notrunc() (default value, see below), this space is given by min(fuse(codomain(t)), fuse(domain(t))). The singular values are contained in Σ and are stored on the diagonal of a (collection of) DenseMatrix instance(s), similar to the eigenvalues before.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The keyword argument trunc provides a way to control the truncation, and is connected to the keyword argument p. The default value notrunc() implies no truncation, and thus ϵ = 0. Other valid options are","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"truncerr(η::Real): truncates such that the p-norm of the truncated singular values is smaller than η times the p-norm of all singular values;\ntruncdim(χ::Integer): finds the optimal truncation such that the equivalent total dimension of the internal vector space is no larger than χ;\ntruncspace(W): truncates such that the dimension of the internal vector space is smaller than that of W in any sector, i.e. with W₀ = min(fuse(codomain(t)), fuse(domain(t))) this option will result in domain(U) == domain(Σ) == codomain(Σ) == codomain(Vᵈ) == min(W, W₀);\ntrunbelow(η::Real): truncates such that every singular value is larger then η; this is different from truncerr(η) with p = Inf because it works in absolute rather than relative values.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, the alg keyword can be either SVD() or SDD() (default), which corresponds to two different algorithms in LAPACK to compute singular value decompositions. The default value SDD() uses a divide-and-conquer algorithm and is typically the fastest, but can loose some accuracy. The SVD() method uses a QR-iteration scheme and can be more accurate, but is typically slower. Since Julia 1.3, these two algorithms are also available in the LinearAlgebra standard library, where they are specified as LinearAlgebra.DivideAndConquer() and LinearAlgebra.QRIteration().","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that we defined the new method tsvd (truncated or tensor singular value decomposition), rather than overloading LinearAlgebra.svd. We (will) also support LinearAlgebra.svd(t) as alternative for tsvd(t; trunc = notrunc()), but note that the return values are then given by U, Σ, V = svd(t) with V = adjoint(Vʰ).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"We also define the following pair of orthogonal factorization algorithms, which are useful when one is not interested in truncating a tensor or knowing the singular values, but only in its image or coimage.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Q, R = leftorth(t; alg::OrthogonalFactorizationAlgorithm = QRpos(), kwargs...): this produces an isometry Q::TensorMap{S,N₁,1} (i.e. Q'*Q approximates the identity, Q*Q' is an idempotent, i.e. squares to itself) and a general tensor map R::TensorMap{1,N₂}, such that t ≈ Q*R. Here, the domain of Q and thus codomain of R is a single vector space of type S that is typically given by min(fuse(codomain(t)), fuse(domain(t))).\nThe underlying algorithm used to compute this decomposition can be chosen among QR(), QRpos(), QL(), QLpos(), SVD(), SDD(), Polar(). QR() uses the underlying qr decomposition from LinearAlgebra, while QRpos() (the default) adds a correction to that to make sure that the diagonal elements of R are positive. Both result in upper triangular R, which are square when codomain(t) ≾ domain(t) and wide otherwise. QL() and QLpos() similarly result in a lower triangular matrices in R, but only work in the former case, i.e. codomain(t) ≾ domain(t), which amounts to blockdim(codomain(t), c) >= blockdim(domain(t), c) for all c ∈ blocksectors(t).\nOne can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then set Q=U and R=Σ*Vʰ from the corresponding singular value decomposition, where only these singular values σ >= max(atol, norm(t)*rtol) (and corresponding singular vectors in U) are kept. More finegrained control on the chosen singular values can be obtained with tsvd and its trunc keyword.\nFinally, Polar() sets Q=U*Vʰ and R = (Vʰ)'*Σ*Vʰ, such that R is positive definite; in this case SDD() is used to actually compute the singular value decomposition and no atol or rtol can be provided.\nL, Q = rightorth(t; alg::OrthogonalFactorizationAlgorithm = QRpos()): this produces a general tensor map L::TensorMap{S,N₁,1} and the adjoint of an isometry Q::TensorMap{S,1,N₂}, such that t ≈ L*Q. Here, the domain of L and thus codomain of Q is a single vector space of type S that is typically given by min(fuse(codomain(t)), fuse(domain(t))).\nThe underlying algorithm used to compute this decomposition can be chosen among LQ(), LQpos(), RQ(), RQpos(), SVD(), SDD(), Polar(). LQ() uses the underlying qr decomposition from LinearAlgebra on the transposed data, and leads to lower triangular matrices in L; LQpos() makes sure the diagonal elements are positive. The matrices L are square when codomain(t) ≿ domain(t) and tall otherwise. Similarly, RQ() and RQpos() result in upper triangular matrices in L, but only works if codomain(t) ≿ domain(t), i.e. when blockdim(codomain(t), c) <= blockdim(domain(t), c) for all c ∈ blocksectors(t).\nOne can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then set L=U*Σ and Q=Vʰ from the corresponding singular value decomposition, where only these singular values σ >= max(atol, norm(t)*rtol) (and corresponding singular vectors in Vʰ) are kept. More finegrained control on the chosen singular values can be obtained with tsvd and its trunc keyword.\nFinally, Polar() sets L = U*Σ*U' and Q=U*Vʰ, such that L is positive definite; in this case SDD() is used to actually compute the singular value decomposition and no atol or rtol can be provided.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Furthermore, we can compute an orthonormal basis for the orthogonal complement of the image and of the co-image (i.e. the kernel) with the following methods:","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"N = leftnull(t; alg::OrthogonalFactorizationAlgorithm = QR(), kwargs...): returns an isometric TensorMap{S,N₁,1} (i.e. N'*N approximates the identity) such that N'*t is approximately zero.\nHere, alg can be QR() (QRpos() acts identically in this case), which assumes that t is full rank in all of its blocks and only returns an orthonormal basis for the missing columns.\nIf this is not the case, one can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then construct N from the left singular vectors corresponding to singular values σ < max(atol, norm(t)*rtol).\nN = rightnull(t; alg::OrthogonalFactorizationAlgorithm = QR(), kwargs...): returns a TensorMap{S,1,N₂} with isometric adjoint (i.e. N*N' approximates the identity) such that t*N' is approximately zero.\nHere, alg can be LQ() (LQpos() acts identically in this case), which assumes that t is full rank in all of its blocks and only returns an orthonormal basis for the missing rows.\nIf this is not the case, one can also use alg = SVD() or alg = SDD(), with extra keywords to control the absolute (atol) or relative (rtol) tolerance. We then construct N from the right singular vectors corresponding to singular values σ < max(atol, norm(t)*rtol).","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Note that the methods leftorth, rightorth, leftnull and rightnull also come in a form with exclamation mark, i.e. leftorth!, rightorth!, leftnull! and rightnull!, which destroy the input tensor t.","category":"page"},{"location":"man/tensors/#Factorizations-for-custom-index-bipartions","page":"Tensors and the TensorMap type","title":"Factorizations for custom index bipartions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Finally, note that each of the factorizations take a single argument, the tensor map t, and a number of keyword arguments. They perform the factorization according to the given codomain and domain of the tensor map. In many cases, we want to perform the factorization according to a different bipartition of the indices. When BraidingStyle(sectortype(t)) isa SymmetricBraiding, we can immediately specify an alternative bipartition of the indices of t in all of these methods, in the form","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"factorize(t::AbstracTensorMap, pleft::NTuple{N₁′,Int}, pright::NTuple{N₂′,Int}; kwargs...)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where pleft will be the indices in the codomain of the new tensor map, and pright the indices of the domain. Here, factorize is any of the methods LinearAlgebra.eigen, eig, eigh, tsvd, LinearAlgebra.svd, leftorth, rightorth, leftnull and rightnull. This signature does not allow for the exclamation mark, because it amounts to","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"factorize!(permute(t, pleft, pright; copy = true); kwargs...)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where permute was introduced and discussed in the previous section. When the braiding is not symmetric, the user should manually apply braid to bring the tensor map in proper form before performing the factorization.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Some examples to conclude this section","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"V1 = SU₂Space(0=>2,1/2=>1)\nV2 = SU₂Space(0=>1,1/2=>1,1=>1)\n\nt = randn(V1 ⊗ V1, V2);\nU, S, W = tsvd(t);\nt ≈ U * S * W\nD, V = eigh(t'*t);\nD ≈ S*S\nU'*U ≈ id(domain(U))\nS\n\nQ, R = leftorth(t; alg = Polar());\nisposdef(R)\nQ ≈ U*W\nR ≈ W'*S*W\n\nU2, S2, W2, ε = tsvd(t; trunc = truncspace(V1));\nW2*W2' ≈ id(codomain(W2))\nS2\nε ≈ norm(block(S, Irrep[SU₂](1)))*sqrt(dim(Irrep[SU₂](1)))\n\nL, Q = rightorth(t, (1,), (2,3));\ncodomain(L), domain(L), domain(Q)\nQ*Q'\nP = Q'*Q;\nP ≈ P*P\nt′ = permute(t, (1,), (2,3));\nt′ ≈ t′ * P","category":"page"},{"location":"man/tensors/#ss_tensor_contraction","page":"Tensors and the TensorMap type","title":"Bosonic tensor contractions and tensor networks","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"One of the most important operation with tensor maps is to compose them, more generally known as contracting them. As mentioned in the section on category theory, a typical composition of maps in a ribbon category can graphically be represented as a planar arrangement of the morphisms (i.e. tensor maps, boxes with lines eminating from top and bottom, corresponding to source and target, i.e. domain and codomain), where the lines connecting the source and targets of the different morphisms should be thought of as ribbons, that can braid over or underneath each other, and that can twist. Technically, we can embed this diagram in ℝ 01 and attach all the unconnected line endings corresponding objects in the source at some position (x0) for xℝ, and all line endings corresponding to objects in the target at some position (x1). The resulting morphism is then invariant under what is known as framed three-dimensional isotopy, i.e. three-dimensional rearrangements of the morphism that respect the rules of boxes connected by ribbons whose open endings are kept fixed. Such a two-dimensional diagram cannot easily be encoded in a single line of code.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"However, things simplify when the braiding is symmetric (such that over- and under- crossings become equivalent, i.e. just crossings), and when twists, i.e. self-crossings in this case, are trivial. This amounts to BraidingStyle(I) == Bosonic() in the language of TensorKit.jl, and is true for any subcategory of mathbfVect, i.e. ordinary tensors, possibly with some symmetry constraint. The case of mathbfSVect and its subcategories, and more general categories, are discussed below.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"In the case of triival twists, we can deform the diagram such that we first combine every morphism with a number of coevaluations η so as to represent it as a tensor, i.e. with a trivial domain. We can then rearrange the morphism to be all ligned up horizontally, where the original morphism compositions are now being performed by evaluations ϵ. This process will generate a number of crossings and twists, where the latter can be omitted because they act trivially. Similarly, double crossings can also be omitted. As a consequence, the diagram, or the morphism it represents, is completely specified by the tensors it is composed of, and which indices between the different tensors are connect, via the evaluation ϵ, and which indices make up the source and target of the resulting morphism. If we also compose the resulting morphisms with coevaluations so that it has a trivial domain, we just have one type of unconnected lines, henceforth called open indices. We sketch such a rearrangement in the following picture","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor unitary)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Hence, we can now specify such a tensor diagram, henceforth called a tensor contraction or also tensor network, using a one-dimensional syntax that mimicks abstract index notation and specifies which indices are connected by the evaluation map using Einstein's summation conventation. Indeed, for BraidingStyle(I) == Bosonic(), such a tensor contraction can take the same format as if all tensors were just multi-dimensional arrays. For this, we rely on the interface provided by the package TensorOperations.jl.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"The above picture would be encoded as","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[a,b,c,d,e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[:] := A[1,2,-4,3]*B[4,5,-3,3]*C[1,-5,4,-2]*D[-1,2,5]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where the latter syntax is known as NCON-style, and labels the unconnected or outgoing indices with negative integers, and the contracted indices with positive integers.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A number of remarks are in order. TensorOperations.jl accepts both integers and any valid variable name as dummy label for indices, and everything in between [ ] is not resolved in the current context but interpreted as a dummy label. Here, we label the indices of a TensorMap, like A::TensorMap{S,N₁,N₂}, in a linear fashion, where the first position corresponds to the first space in codomain(A), and so forth, up to position N₁. Index N₁+1then corresponds to the first space in domain(A). However, because we have applied the coevaluation η, it actually corresponds to the corresponding dual space, in accordance with the interface of space(A, i) that we introduced above, and as indiated by the dotted box around A in the above picture. The same holds for the other tensor maps. Note that our convention also requires that we braid indices that we brought from the domain to the codomain, and so this is only unambiguous for a symmetric braiding, where there is a unique way to permute the indices.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"With the current syntax, we create a new object E because we use the definition operator :=. Furthermore, with the current syntax, it will be a Tensor, i.e. it will have a trivial domain, and correspond to the dotted box in the picture above, rather than the actual morphism E. We can also directly define E with the correct codomain and domain by rather using","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[a b c;d e] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[(a,b,c);(d,e)] := A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"where the latter syntax can also be used when the codomain is empty. When using the assignment operator =, the TensorMap E is assumed to exist and the contents will be written to the currently allocated memory. Note that for existing tensors, both on the left hand side and right hand side, trying to specify the indices in the domain and the codomain seperately using the above syntax, has no effect, as the bipartition of indices are already fixed by the existing object. Hence, if E has been created by the previous line of code, all of the following lines are now equivalent","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor E[(a,b,c);(d,e)] = A[v,w,d,x]*B[y,z,c,x]*C[v,e,y,b]*D[a,w,z]\n@tensor E[a,b,c,d,e] = A[v w d;x]*B[(y,z,c);(x,)]*C[v e y; b]*D[a,w,z]\n@tensor E[a b; c d e] = A[v; w d x]*B[y,z,c,x]*C[v,e,y,b]*D[a w;z]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"and none of those will or can change the partition of the indices of E into its codomain and its domain.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Two final remarks are in order. Firstly, the order of the tensors appearing on the right hand side is irrelevant, as we can reorder them by using the allowed moves of the Penrose graphical calculus, which yields some crossings and a twist. As the latter is trivial, it can be omitted, and we just use the same rules to evaluate the newly ordered tensor network. For the particular case of matrix matrix multiplication, which also captures more general settings by appropriotely combining spaces into a single line, we indeed find","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"(Image: tensor contraction reorder)","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"or thus, the following to lines of code yield the same result","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor C[i,j] := B[i,k]*A[k,j]\n@tensor C[i,j] := A[k,j]*B[i,k]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Reordering of tensors can be used internally by the @tensor macro to evaluate the contraction in a more efficient manner. In particular, the NCON-style of specifying the contraction gives the user control over the order, and there are other macros, such as @tensoropt, that try to automate this process. There is also an @ncon macro and ncon function, an we recommend reading the manual of TensorOperations.jl to learn more about the possibilities and how they work.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"A final remark involves the use of adjoints of tensors. The current framework is such that the user should not be to worried about the actual bipartition into codomain and domain of a given TensorMap instance. Indeed, for factorizations one just specifies the requested bipartition via the factorize(t, pleft, pright) interface, and for tensor contractions the @contract macro figures out the correct manipulations automatically. However, when wanting to use the adjoint of an instance t::TensorMap{S,N₁,N₂}, the resulting adjoint(t) is a AbstractTensorMap{S,N₂,N₁} and one need to know the values of N₁ and N₂ to know exactly where the ith index of t will end up in adjoint(t), and hence to know and understand the index order of t'. Within the @tensor macro, one can instead use conj() on the whole index expression so as to be able to use the original index ordering of t. Indeed, for matrices of thus, TensorMap{S,1,1} instances, this yields exactly the equivalence one expects, namely equivalence between the following to expressions.","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"@tensor C[i,j] := B'[i,k]*A[k,j]\n@tensor C[i,j] := conj(B[k,i])*A[k,j]","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"For e.g. an instance A::TensorMap{S,3,2}, the following two syntaxes have the same effect within an @tensor expression: conj(A[a,b,c,d,e]) and A'[d,e,a,b,c].","category":"page"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"Some examples:","category":"page"},{"location":"man/tensors/#Fermionic-tensor-contractions","page":"Tensors and the TensorMap type","title":"Fermionic tensor contractions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"TODO","category":"page"},{"location":"man/tensors/#Anyonic-tensor-contractions","page":"Tensors and the TensorMap type","title":"Anyonic tensor contractions","text":"","category":"section"},{"location":"man/tensors/","page":"Tensors and the TensorMap type","title":"Tensors and the TensorMap type","text":"TODO","category":"page"},{"location":"man/spaces/#s_spaces","page":"Vector spaces","title":"Vector spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"using TensorKit","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"From the Introduction, it should be clear that an important aspect in the definition of a tensor (map) is specifying the vector spaces and their structure in the domain and codomain of the map. The starting point is an abstract type VectorSpace","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type VectorSpace end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"which is actually a too restricted name. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories, but this can go beyond normal vector spaces (i.e. objects in the category mathbfVect) and even beyond objects of mathbfSVect. However, in order not to make the remaining discussion to abstract or complicated, we will simply refer to subtypes of VectorSpace instead of specific categories, and to spaces (i.e. VectorSpace instances) instead of objects from these categories. In particular, we define two abstract subtypes","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type ElementarySpace <: VectorSpace end\nconst IndexSpace = ElementarySpace\n\nabstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Here, ElementarySpace is a super type for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"On the other hand, subtypes of CompositeSpace{S} where S<:ElementarySpace are composed of a number of elementary spaces of type S. So far, there is a single concrete type ProductSpace{S,N} that represents the tensor product of N vector spaces of a homogeneous type S. Its properties are discussed in the section on Composite spaces, together with possible extensions for the future.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Throughout TensorKit.jl, the function spacetype returns the type of ElementarySpace associated with e.g. a composite space or a tensor. It works both on instances and in the type domain. Its use will be illustrated below.","category":"page"},{"location":"man/spaces/#ss_fields","page":"Vector spaces","title":"Fields","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vector spaces (linear categories) are defined over a field of scalars 𝔽. We define a type hierarchy to specify the scalar field, but so far only support real and complex numbers, via","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type Field end\n\nstruct RealNumbers <: Field end\nstruct ComplexNumbers <: Field end\n\nconst ℝ = RealNumbers()\nconst ℂ = ComplexNumbers()","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that ℝ and ℂ can be typed as \\bbR+TAB and \\bbC+TAB. One reason for defining this new type hierarchy instead of recycling the types from Julia's Number hierarchy is to introduce some syntactic sugar without committing type piracy. In particular, we now have","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"3 ∈ ℝ\n5.0 ∈ ℂ\n5.0+1.0*im ∈ ℝ\nFloat64 ⊆ ℝ\nComplexF64 ⊆ ℂ\nℝ ⊆ ℂ\nℂ ⊆ ℝ","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"and furthermore —probably more usefully— ℝ^n and ℂ^n create specific elementary vector spaces as described in the next section. The underlying field of a vector space or tensor a can be obtained with field(a):","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"field","category":"page"},{"location":"man/spaces/#TensorKit.field-man-spaces","page":"Vector spaces","title":"TensorKit.field","text":"field(V::VectorSpace) -> Field\n\nReturn the field type over which a vector space is defined.\n\n\n\n\n\n","category":"function"},{"location":"man/spaces/#ss_elementaryspaces","page":"Vector spaces","title":"Elementary spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"As mentioned at the beginning of this section, vector spaces that are associated with the individual indices of a tensor should be implemented as subtypes of ElementarySpace. As the domain and codomain of a tensor map will be the tensor product of such objects which all have the same type, it is important that related vector spaces, e.g. the dual space, are objects of the same concrete type (i.e. with the same type parameters in case of a parametric type). In particular, every ElementarySpace should implement the following methods","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"dim(::ElementarySpace) -> ::Int returns the dimension of the space as an Int\ndual(::S) where {S<:ElementarySpace} -> ::S returns the dual space dual(V), using an instance of the same concrete type (i.e. not via type parameters); this should satisfy dual(dual(V))==V\nconj(::S) where {S<:ElementarySpace} -> ::S returns the complex conjugate space conj(V), using an instance of the same concrete type (i.e. not via type parameters); this should satisfy conj(conj(V))==V and we automatically have conj(V::ElementarySpace{ℝ}) = V.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"For convenience, the dual of a space V can also be obtained as V'.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There is concrete type GeneralSpace which is completely characterized by its field 𝔽, its dimension and whether its the dual and/or complex conjugate of 𝔽^d.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct GeneralSpace{𝔽} <: ElementarySpace\n d::Int\n dual::Bool\n conj::Bool\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We furthermore define the trait types","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"abstract type InnerProductStyle end\nstruct NoInnerProduct <: InnerProductStyle end\nabstract type HasInnerProduct <: InnerProductStyle end\nstruct EuclideanInnerProduct <: HasInnerProduct end","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"to denote for a vector space V whether it has an inner product and thus a canonical mapping from dual(V) to V (for real fields 𝔽 ⊆ ℝ) or from dual(V) to conj(V) (for complex fields). This mapping is provided by the metric, but no further support for working with metrics is currently implemented.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Spaces with the EuclideanInnerProduct style have the natural isomorphisms dual(V) == V (for 𝔽 == ℝ) or dual(V) == conj(V) (for 𝔽 == ℂ). In the language of the previous section on categories, this trait represents dagger or unitary categories, and these vector spaces support an adjoint operation.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In particular, the two concrete types","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct CartesianSpace <: ElementarySpace\n d::Int\nend\nstruct ComplexSpace <: ElementarySpace\n d::Int\n dual::Bool\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"represent the Euclidean spaces ℝ^d or ℂ^d without further inner structure. They can be created using the syntax CartesianSpace(d) == ℝ^d and ComplexSpace(d) == ℂ^d, or ComplexSpace(d, true) == ComplexSpace(d; dual = true) == (ℂ^d)' for the dual space of the latter. Note that the brackets are required because of the precedence rules, since d' == d for d::Integer.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Some examples:","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"dim(ℝ^10)\n(ℝ^10)' == ℝ^10\nisdual((ℂ^5))\nisdual((ℂ^5)')\nisdual((ℝ^5)')\ndual(ℂ^5) == (ℂ^5)' == conj(ℂ^5) == ComplexSpace(5; dual = true)\nfield(ℂ^5)\nfield(ℝ^3)\ntypeof(ℝ^3)\nspacetype(ℝ^3)\nInnerProductStyle(ℝ^3)\nInnerProductStyle(ℂ^5)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"note: Note\nFor ℂ^n the dual space is equal (or naturally isomorphic) to the conjugate space, but not to the space itself. This means that even for ℂ^n, arrows matter in the diagrammatic notation for categories or for tensors, and in particular that a contraction between two tensor indices will check that one is living in the space and the other in the dual space. This is in contrast with several other software packages, especially in the context of tensor networks, where arrows are only introduced when discussing symmetries. We believe that our more purist approach can be useful to detect errors (e.g. unintended contractions). Only with ℝ^n will their be no distinction between a space and its dual. When creating tensors with indices in ℝ^n that have complex data, a one-time warning will be printed, but most operations should continue to work nonetheless.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"One more important instance of ElementarySpace is the GradedSpace, which is used to represent a graded complex vector space with Euclidean inner product, where the grading is provided by the irreducible representations of a group, or more generally, the simple objects of a fusion category. We refer to the subsection on graded spaces on the next page for further information about GradedSpace.","category":"page"},{"location":"man/spaces/#ss_compositespaces","page":"Vector spaces","title":"Composite spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Composite spaces are vector spaces that are built up out of individual elementary vector spaces of the same type. The most prominent (and currently only) example is a tensor product of N elementary spaces of the same type S, which is implemented as","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}\n spaces::NTuple{N, S}\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Given some V1::S, V2::S, V3::S of the same type S<:ElementarySpace, we can easily construct ProductSpace{S,3}((V1,V2,V3)) as ProductSpace(V1,V2,V3) or using V1 ⊗ V2 ⊗ V3, where ⊗ is simply obtained by typing \\otimes+TAB. In fact, for convenience, also the regular multiplication operator * acts as tensor product between vector spaces, and as a consequence so does raising a vector space to a positive integer power, i.e.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"V1 = ℂ^2\nV2 = ℂ^3\nV1 ⊗ V2 ⊗ V1' == V1 * V2 * V1' == ProductSpace(V1,V2,V1') == ProductSpace(V1,V2) ⊗ V1'\nV1^3\ndim(V1 ⊗ V2)\ndims(V1 ⊗ V2)\ndual(V1 ⊗ V2)\nspacetype(V1 ⊗ V2)\nspacetype(ProductSpace{ComplexSpace,3})","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Here, the new function dims maps dim to the individual spaces in a ProductSpace and returns the result as a tuple. Note that the rationale for the last result was explained in the subsection on duality in the introduction to category theory.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Following Julia's Base library, the function one applied to a ProductSpace{S,N} returns the multiplicative identity, which is ProductSpace{S,0}(()). The same result is obtained when acting on an instance V of S::ElementarySpace directly, however note that V ⊗ one(V) will yield a ProductSpace{S,1}(V) and not V itself. The same result can be obtained with ⊗(V). Similar to Julia Base, one also works in the type domain.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In the future, other CompositeSpace types could be added. For example, the wave function of an N-particle quantum system in first quantization would require the introduction of a SymmetricSpace{S,N} or a AntiSymmetricSpace{S,N} for bosons or fermions respectively, which correspond to the symmetric (permutation invariant) or antisymmetric subspace of V^N, where V::S represents the Hilbert space of the single particle system. Other domains, like general relativity, might also benefit from tensors living in a subspace with certain symmetries under specific index permutations.","category":"page"},{"location":"man/spaces/#ss_homspaces","page":"Vector spaces","title":"Space of morphisms","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Given that we define tensor maps as morphisms in a 𝕜-linear monoidal category, i.e. linear maps, we also define a type to denote the corresponding space. Indeed, in a 𝕜-linear category C, the set of morphisms mathrmHom(WV) for VW C is always an actual vector space, irrespective of whether or not C is a subcategory of mathbf(S)Vect.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We introduce the type","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}\n codomain::P1\n domain::P2\nend","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"and can create it as either domain → codomain or codomain ← domain (where the arrows are obtained as \\to+TAB or \\leftarrow+TAB, and as \\rightarrow+TAB respectively). The reason for first listing the codomain and than the domain will become clear in the section on tensor maps.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct. However, HomSpace has a number of properties defined, which we illustrate via examples","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"W = ℂ^2 ⊗ ℂ^3 → ℂ^3 ⊗ dual(ℂ^4)\nfield(W)\ndual(W)\nadjoint(W)\nspacetype(W)\nspacetype(typeof(W))\nW[1]\nW[2]\nW[3]\nW[4]\ndim(W)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that indexing W yields first the spaces in the codomain, followed by the dual of the spaces in the domain. This particular convention is useful in combination with the instances of type TensorMap, which represent morphisms living in such a HomSpace. Also note that dim(W) here seems to be the product of the dimensions of the individual spaces, but that this is no longer true once symmetries are involved. At any time will dim(::HomSpace) represent the number of linearly independent morphisms in this space.","category":"page"},{"location":"man/spaces/#Partial-order-among-vector-spaces","page":"Vector spaces","title":"Partial order among vector spaces","text":"","category":"section"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vector spaces of the same spacetype can be given a partial order, based on whether there exist injective morphisms (a.k.a monomorphisms) or surjective morphisms (a.k.a. epimorphisms) between them. In particular, we define ismonomorphic(V1, V2), with Unicode synonym V1 ≾ V2 (obtained as \\precsim+TAB), to express whether there exist monomorphisms in V1→V2. Similarly, we define isepimorphic(V1, V2), with Unicode synonym V1 ≿ V2 (obtained as \\succsim+TAB), to express whether there exist epimorphisms in V1→V2. Finally, we define isisomorphic(V1, V2), with Unicode alternative V1 ≅ V2 (obtained as \\cong+TAB), to express whether there exist isomorphism in V1→V2. In particular V1 ≅ V2 if and only if V1 ≾ V2 && V1 ≿ V2.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"For completeness, we also export the strict comparison operators ≺ and ≻ (\\prec+TAB and \\succ+TAB), with definitions","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"≺(V1::VectorSpace, V2::VectorSpace) = V1 ≾ V2 && !(V1 ≿ V2)\n≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2)","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"However, as we expect these to be less commonly used, no ASCII alternative is provided.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In the context of InnerProductStyle(V) <: EuclideanInnerProduct, V1 ≾ V2 implies that there exists isometries WV1 V2 such that W^ W = mathrmid_V1, while V1 ≅ V2 implies that there exist unitaries UV1V2 such that U^ U = mathrmid_V1 and U U^ = mathrmid_V2.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Note that spaces that are isomorphic are not necessarily equal. One can be a dual space, and the other a normal space, or one can be an instance of ProductSpace, while the other is an ElementarySpace. There will exist (infinitely) many isomorphisms between the corresponding spaces, but in general none of those will be canonical.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There are also a number of convenience functions to create isomorphic spaces. The function fuse(V1, V2, ...) or fuse(V1 ⊗ V2 ⊗ ...) returns an elementary space that is isomorphic to V1 ⊗ V2 ⊗ .... The function flip(V::ElementarySpace) returns a space that is isomorphic to V but has isdual(flip(V)) == isdual(V'), i.e. if V is a normal space than flip(V) is a dual space. flip(V) is different from dual(V) in the case of GradedSpace. It is useful to flip a tensor index from a ket to a bra (or vice versa), by contracting that index with a unitary map from V1 to flip(V1). We refer to the reference on vector space methods for further information. Some examples:","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"ℝ^3 ≾ ℝ^5\nℂ^3 ≾ (ℂ^5)'\n(ℂ^5) ≅ (ℂ^5)'\nfuse(ℝ^5, ℝ^3)\nfuse(ℂ^3, (ℂ^5)' ⊗ ℂ^2)\nfuse(ℂ^3, (ℂ^5)') ⊗ ℂ^2 ≅ fuse(ℂ^3, (ℂ^5)', ℂ^2) ≅ ℂ^3 ⊗ (ℂ^5)' ⊗ ℂ^2\nflip(ℂ^4)\nflip(ℂ^4) ≅ ℂ^4\nflip(ℂ^4) == ℂ^4","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"We also define the direct sum V1 and V2 as V1 ⊕ V2, where ⊕ is obtained by typing \\oplus+TAB. This is possible only if isdual(V1) == isdual(V2). With a little pun on Julia Base, oneunit applied to an elementary space (in the value or type domain) returns the one-dimensional space, which is isomorphic to the scalar field of the space itself. Some examples illustrate this better","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"ℝ^5 ⊕ ℝ^3\nℂ^5 ⊕ ℂ^3\nℂ^5 ⊕ (ℂ^3)'\noneunit(ℝ^3)\nℂ^5 ⊕ oneunit(ComplexSpace)\noneunit((ℂ^3)')\n(ℂ^5) ⊕ oneunit((ℂ^5))\n(ℂ^5)' ⊕ oneunit((ℂ^5)')","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Finally, while spaces have a partial order, there is no unique infimum or supremum of a two or more spaces. However, if V1 and V2 are two ElementarySpace instances with isdual(V1) == isdual(V2), then we can define a unique infimum V::ElementarySpace with the same value of isdual that satisfies V ≾ V1 and V ≾ V2, as well as a unique supremum W::ElementarySpace with the same value of isdual that satisfies W ≿ V1 and W ≿ V2. For CartesianSpace and ComplexSpace, this simply amounts to the space with minimal or maximal dimension, i.e.","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"infimum(ℝ^5, ℝ^3)\nsupremum(ℂ^5, ℂ^3)\nsupremum(ℂ^5, (ℂ^3)')","category":"page"},{"location":"man/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The names infimum and supremum are especially suited in the case of GradedSpace, as the infimum of two spaces might be different from either of those two spaces, and similar for the supremum.","category":"page"},{"location":"man/sectors/#s_sectorsrepfusion","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"using TensorKit\nimport LinearAlgebra","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Symmetries in a physical system often result in tensors which are invariant under the action of the symmetry group, where this group acts as a tensor product of group actions on every tensor index separately. The group action on a single index, or thus, on the corresponding vector space, can be decomposed into irreducible representations (irreps). Here, we restrict to unitary representations, such that the corresponding vector spaces also have a natural Euclidean inner product. In particular, the Euclidean inner product between two vectors is invariant under the group action and thus transforms according to the trivial representation of the group.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The corresponding vector spaces will be canonically represented as V = _a ℂ^n_a R_a, where a labels the different irreps, n_a is the number of times irrep a appears and R_a is the vector space associated with irrep a. Irreps are also known as spin sectors (in the case of mathsfSU_2) or charge sectors (in the case of mathsfU_1), and we henceforth refer to a as a sector. As discussed in the section on categories, and briefly summarized below, the approach we follow does in fact go beyond the case of irreps of groups, and sectors would more generally correspond to simple objects in a unitary ribbon fusion category. Nonetheless, every step can be appreciated by using the representation theory of mathsfSU_2 or mathsfSU_3 as example. For practical reasons, we assume that there is a canonical order of the sectors, so that the vector space V is completely specified by the values of n_a.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The gain in efficiency (both in memory occupation and computation time) obtained from using (technically: equivariant) tensor maps is that, by Schur's lemma, they are block diagonal in the basis of coupled sectors. To exploit this block diagonal form, it is however essential that we know the basis transform from the individual (uncoupled) sectors appearing in the tensor product form of the domain and codomain, to the totally coupled sectors that label the different blocks. We refer to the latter as block sectors. The transformation from the uncoupled sectors in the domain (or codomain) of the tensor map to the block sector is encoded in a fusion tree (or splitting tree). Essentially, it is a sequential application of pairwise fusion as described by the group's Clebsch–Gordan (CG) coefficients. However, it turns out that we do not need the actual CG coefficients, but only how they transform under transformations such as interchanging the order of the incoming irreps or interchanging incoming and outgoing irreps. This information is known as the topological data of the group, i.e. mainly the F-symbols, which are also known as recoupling coefficients or 6j-symbols (more accurately, the F-symbol is actually Racah's W-coefficients in the case of mathsfSU_2).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Below, we describe how to specify a certain type of sector and what information about them needs to be implemented. Then, we describe how to build a space V composed of a direct sum of different sectors. In the third section, we explain the details of fusion trees, i.e. their construction and manipulation. Finally, we elaborate on the case of general fusion categories and the possibility of having fermionic or anyonic twists. But first, we provide a quick theoretical overview of the required data of the representation theory of a group. We refer to the section on categories, and in particular the subsection on topological data of a unitary fusion category, for further details.","category":"page"},{"location":"man/sectors/#ss_representationtheory","page":"Sectors, graded spaces and fusion trees","title":"Representation theory and unitary fusion categories","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Let the different irreps or sectors be labeled as a, b, c, … First and foremost, we need to specify the fusion rules a b = N^ab_c c with N^ab_c some non-negative integers. There should always exists a unique trivial sector u (called the identity object I or 1 in the language of categories) such that a u = a = u a. Furthermore, there should exist a unique sector bara such that N^abara_u = 1, whereas for all b neq bara, N^ab_u = 0. For unitary irreps of groups, bara corresponds to the complex conjugate of the representation a, or a representation isomorphic to it. For example, for the representations of mathsfSU_2, the trivial sector corresponds to spin zero and all irreps are self-dual (i.e. a = bara), meaning that the conjugate representation is isomorphic to the non-conjugated one (they are however not equal but related by a similarity transform).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The meaning of the fusion rules is that the space of transformations R_a R_b R_c (or vice versa) has dimension N^ab_c. In particular, we assume the existence of a basis consisting of unitary tensor maps X^ab_cμ R_c R_a R_b with μ = 1 N^ab_c such that","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(X^ab_cμ)^ X^ab_cν = δ_μν mathrmid_R_c","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"sum_c sum_μ = 1^N^ab_c X^ab_cμ (X^ab_cμ)^dagger = mathrmid_R_a R_b","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The tensors X^ab_cμ are the splitting tensors, their hermitian conjugate are the fusion tensors. They are only determined up to a unitary basis transform within the space, i.e. acting on the multiplicity label μ = 1 N^ab_c. For mathsfSU_2, where N^ab_c is zero or one and the multiplicity labels are absent, the entries of X^ab_cμ are precisely given by the CG coefficients. The point is that we do not need to know the tensors X^ab_cμ explicitly, but only the topological data of (the representation category of) the group, which describes the following transformation:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"F-move or recoupling: the transformation between (a b) c to a (b c):\n(X^ab_eμ mathrmid_c) X^ec_dν = _fκλ F^abc_d_eμν^fκλ (mathrmid_a X^bc_fκ) X^af_dλ\nBraiding or permuting as defined by τ_ab R_a R_b R_b R_a: τ_R_aR_b X^ab_cμ = _ν R^ab_c^ν_μ X^ba_cν","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The dimensions of the spaces R_a on which representation a acts are denoted as d_a and referred to as quantum dimensions. In particular d_u = 1 and d_a = d_bara. This information is also encoded in the F-symbol as d_a = F^a bara a_a^u_u ^-1. Note that there are no multiplicity labels in that particular F-symbol as N^abara_u = 1.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is a graphical representation associated with the fusion tensors and their manipulations, which we summarize here:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: summary)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As always, we refer to the subsection on topological data of a unitary fusion category for further details.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Finally, for the implementation, it will be useful to distinguish between a number of different possibilities regarding the fusion rules. If, for every a and b, there is a unique c such that a b = c (i.e. N^ab_c = 1 and N^ab_c = 0 for all other c), the category is abelian. Indeed, the representations of a group have this property if and only if the group multiplication law is commutative. In that case, all spaces R_a associated with the representation are one-dimensional and thus trivial. In all other cases, the category is non-abelian. We find it useful to further distinguish between categories which have all N^ab_c equal to zero or one (such that no multiplicity labels are needed), e.g. the representations of mathsfSU_2, and those where some N^ab_c are larger than one, e.g. the representations of mathsfSU_3.","category":"page"},{"location":"man/sectors/#ss_sectors","page":"Sectors, graded spaces and fusion trees","title":"Sectors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We introduce a new abstract type to represent different possible sectors","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type Sector end","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Any concrete subtype of Sector should be such that its instances represent a consistent set of sectors, corresponding to the irreps of some group, or, more generally, the simple objects of a (unitary) fusion category, as reviewed in the subsections on fusion categories and their topological data within the introduction to category theory. Throughout TensorKit.jl, the method sectortype can be used to query the subtype of Sector associated with a particular object, i.e. a vector space, fusion tree, tensor map, or a sector. It works on both instances and in the type domain, and its use will be illustrated further on.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The minimal data to completely specify a type of sector are","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"the fusion rules, i.e. a b = N^ab_c c; this is implemented by a function Nsymbol(a,b,c)\nthe list of fusion outputs from a b; while this information is contained in N^ab_c, it might be costly or impossible to iterate over all possible values of c and test Nsymbol(a,b,c); instead we implement for a ⊗ b to return an iterable object (e.g. tuple, array or a custom Julia type that listens to Base.iterate) and which generates all c for which N^ab_c 0 (just once even if N^ab_c1)\nthe identity object u, such that a u = a = u a; this is implemented by the function one(a) (and also in type domain) from Julia Base\nthe dual or conjugate representation overlinea for which N^abara_u = 1; this is implemented by conj(a) from Julia Base; dual(a) also works as alias, but conj(a) is the method that should be defined\nthe F-symbol or recoupling coefficients F^abc_d^f_e, implemented as the function Fsymbol(a,b,c,d,e,f)\nthe R-symbol R^ab_c, implemented as the function Rsymbol(a,b,c)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For practical reasons, we also require some additional methods to be defined:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"isreal(::Type{<:Sector}) returns whether the topological data of this type of sector is real-valued or not (in which case it is complex-valued). Note that this does not necessarily require that the representation itself, or the Clebsch-Gordan coefficients, are real. There is a fallback implementation that checks whether the F-symbol and R-symbol evaluated with all sectors equal to the identity sector have real eltype.\nhash(a, h) creates a hash of sectors, because sectors and objects created from them are used as keys in lookup tables (i.e. dictionaries)\nisless(a,b) associates a canonical order to sectors (of the same type), in order to unambiguously represent representation spaces V = _a ℂ^n_a R_a.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Further information, such as the quantum dimensions d_a and Frobenius-Schur indicator χ_a (only if a == overlinea) are encoded in the F-symbol. They are obtained as dim(a) and frobeniusschur(a). These functions have default definitions which extract the requested data from Fsymbol(a,conj(a),a,a,one(a),one(a)), but they can be overloaded in case the value can be computed more efficiently.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We also define a parametric type to represent an indexable iterator over the different values of a sector as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SectorValues{I<:Sector} end\nBase.IteratorEltype(::Type{<:SectorValues}) = HasEltype()\nBase.eltype(::Type{SectorValues{I}}) where {I<:Sector} = I\nBase.values(::Type{I}) where {I<:Sector} = SectorValues{I}()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that an instance of the singleton type SectorValues{I} is obtained as values(I). A new sector I<:Sector should define","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.iterate(::SectorValues{I}[, state]) = ...\nBase.IteratorSize(::Type{SectorValues{I}}) = # HasLenght() or IsInfinite()\n# if previous function returns HasLength():\nBase.length(::SectorValues{I}) = ...\nBase.getindex(::SectorValues{I}, i::Int) = ...\nfindindex(::SectorValues{I}, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If the number of values in a sector I is finite (i.e. IteratorSize(values(I)) == HasLength()), the methods getindex and findindex provide a way to map the different sector values from and to the standard range 1, 2, …, length(values(I)). This is used to efficiently represent GradedSpace objects for this type of sector, as discussed in the next section on Graded spaces. Note that findindex acts similar to Base.indexin, but with the order of the arguments reversed (so that is more similar to getindex), and returns an Int rather than an Array{0,Union{Int,Nothing}}.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"It is useful to distinguish between three cases with respect to the fusion rules. For irreps of Abelian groups, we have that for every a and b, there exists a unique c such that a b = c, i.e. there is only a single fusion channel. This follows simply from the fact that all irreps are one-dimensional. All other cases are referred to as non-abelian, i.e. the irreps of a non-abelian group or some more general fusion category. We still distinguish between the case where all entries of N^ab_c 1, i.e. they are zero or one. In that case, F^abc_d^f_e and R^ab_c are scalars. If some N^ab_c 1, it means that the same sector c can appear more than once in the fusion product of a and b, and we need to introduce some multiplicity label μ for the different copies. We implement a \"trait\" (similar to IndexStyle for AbstractArrays in Julia Base), i.e. a type hierarchy","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type FusionStyle end\nstruct UniqueFusion <: FusionStyle # unique fusion output when fusion two sectors\nend\nabstract type MultipleFusion <: FusionStyle end\nstruct SimpleFusion <: MultipleFusion # multiple fusion but multiplicity free\nend\nstruct GenericFusion <: MultipleFusion # multiple fusion with multiplicities\nend\nconst MultiplicityFreeFusion = Union{UniqueFusion, SimpleFusion}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"New sector types I<:Sector should then indicate which fusion style they have by defining FusionStyle(::Type{I}).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In a similar manner, it is useful to distinguish between different styles of braiding. Remember that for group representations, braiding acts as swapping or permuting the vector spaces involved. By definition, applying this operation twice leads us back to the original situation. If that is the case, the braiding is said to be symmetric. For more general fusion categories, associated with the physics of anyonic particles, this is generally not the case and, as a result, permutations of tensor indices are not unambiguously defined. The correct description is in terms of the braid group. This will be discussed in more detail below. Fermions are somewhat in between, as their braiding is symmetric, but they have a non-trivial twist. We thereto define a new type hierarchy","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type BraidingStyle end # generic braiding\nabstract type SymmetricBraiding <: BraidingStyle end\nstruct Bosonic <: SymmetricBraiding end\nstruct Fermionic <: SymmetricBraiding end\nstruct Anyonic <: BraidingStyle end","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"New sector types I<:Sector should then indicate which fusion style they have by defining BraidingStyle(::Type{I}). Note that Bosonic() braiding does not mean that all permutations are trivial and R^ab_c = 1, but that R^ab_c R^ba_c = 1. For example, for the irreps of mathsfSU_2, the R-symbol associated with the fusion of two spin-1/2 particles to spin zero is -1, i.e. the singlet of two spin-1/2 particles is antisymmetric. For a Bosonic() braiding style, all twists are simply +1. The case of fermions and anyons are discussed below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Before discussing in more detail how a new sector type should be implemented, let us study the cases which have already been implemented. Currently, they all correspond to the irreps of groups.","category":"page"},{"location":"man/sectors/#sss_groups","page":"Sectors, graded spaces and fusion trees","title":"Existing group representations","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first sector type is called Trivial, and corresponds to the case where there is actually no symmetry, or thus, the symmetry is the trivial group with only an identity operation and a trivial representation. Its representation theory is particularly simple:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct Trivial <: Sector\nend\nBase.one(a::Sector) = one(typeof(a))\nBase.one(::Type{Trivial}) = Trivial()\nBase.conj(::Trivial) = Trivial()\n⊗(::Trivial, ::Trivial) = (Trivial(),)\nNsymbol(::Trivial, ::Trivial, ::Trivial) = true\nFsymbol(::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial) = 1\nRsymbol(::Trivial, ::Trivial, ::Trivial) = 1\nBase.isreal(::Type{Trivial}) = true\nFusionStyle(::Type{Trivial}) = UniqueFusion()\nBraidingStyle(::Type{Trivial}) = Bosonic()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The Trivial sector type is special cased in the construction of tensors, so that most of these definitions are not actually used.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The most important class of sectors are irreducible representations of groups, for which we have an abstract supertype Irrep{G} that is parameterized on the type of group G. While the specific implementations of Irrep{G} depend on G, one can easily obtain the concrete type without knowing its name as Irrep[G].","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"A number of groups have been defined, namely","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type Group end\nabstract type AbelianGroup <: Group end\n\nabstract type ℤ{N} <: AbelianGroup end\nabstract type U₁ <: AbelianGroup end\nabstract type SU{N} <: Group end\nabstract type CU₁ <: Group end\n\nconst ℤ₂ = ℤ{2}\nconst ℤ₃ = ℤ{3}\nconst ℤ₄ = ℤ{4}\nconst SU₂ = SU{2}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Groups themselves are abstract types without any functionality (at least for now). We also provide a number of convenient Unicode aliases. These group names are probably self- explanatory, except for CU₁ which is explained below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For all group irreps, the braiding style is bosonic","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"abstract type AbstractIrrep{G<:Group} <: Sector end # irreps have integer quantum dimensions\nBraidingStyle(::Type{<:AbstractIrrep}) = Bosonic()","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"while we gather some more common functionality for irreps of abelian groups (which exhaust all possibilities of fusion categories with abelian fusion)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"const AbelianIrrep{G} = AbstractIrrep{G} where {G<:AbelianGroup}\nFusionStyle(::Type{<:AbelianIrrep}) = UniqueFusion()\nBase.isreal(::Type{<:AbelianIrrep}) = true\n\nNsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = c == first(a ⊗ b)\nFsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:AbelianIrrep} =\n Int(Nsymbol(a, b, e)*Nsymbol(e, c, d)*Nsymbol(b, c, f)*Nsymbol(a, f, d))\nfrobeniusschur(a::AbelianIrrep) = 1\nBsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))\nRsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With these common definition, we implement the representation theory of the two most common Abelian groups, namely ℤ_N","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}\n n::Int8\n function ZNIrrep{N}(n::Integer) where {N}\n @assert N < 64\n new{N}(mod(n, N))\n end\nend\nBase.getindex(::IrrepTable, ::Type{ℤ{N}}) where N = ZNIrrep{N}\nBase.convert(Z::Type{<:ZNIrrep}, n::Real) = Z(n)\n\nBase.one(::Type{ZNIrrep{N}}) where {N} =ZNIrrep{N}(0)\nBase.conj(c::ZNIrrep{N}) where {N} = ZNIrrep{N}(-c.n)\n⊗(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = (ZNIrrep{N}(c1.n+c2.n),)\n\nBase.IteratorSize(::Type{SectorValues{ZNIrrep{N}}}) where N = HasLength()\nBase.length(::SectorValues{ZNIrrep{N}}) where N = N\nBase.iterate(::SectorValues{ZNIrrep{N}}, i = 0) where N =\n return i == N ? nothing : (ZNIrrep{N}(i), i+1)\nBase.getindex(::SectorValues{ZNIrrep{N}}, i::Int) where N =\n 1 <= i <= N ? ZNIrrep{N}(i-1) : throw(BoundsError(values(ZNIrrep{N}), i))\nfindindex(::SectorValues{ZNIrrep{N}}, c::ZNIrrep{N}) where N = c.n + 1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and mathsfU_1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct U1Irrep <: AbstractIrrep{U₁}\n charge::HalfInt\nend\nBase.getindex(::IrrepTable, ::Type{U₁}) = U1Irrep\nBase.convert(::Type{U1Irrep}, c::Real) = U1Irrep(c)\n\nBase.one(::Type{U1Irrep}) = U1Irrep(0)\nBase.conj(c::U1Irrep) = U1Irrep(-c.charge)\n⊗(c1::U1Irrep, c2::U1Irrep) = (U1Irrep(c1.charge+c2.charge),)\n\nBase.IteratorSize(::Type{SectorValues{U1Irrep}}) = IsInfinite()\nBase.iterate(::SectorValues{U1Irrep}, i = 0) =\n return i <= 0 ? (U1Irrep(half(i)), (-i + 1)) : (U1Irrep(half(i)), -i)\n# the following are not used and thus not really necessary\nfunction Base.getindex(::SectorValues{U1Irrep}, i::Int)\n i < 1 && throw(BoundsError(values(U1Irrep), i))\n return U1Irrep(iseven(i) ? half(i>>1) : -half(i>>1))\nend\nfindindex(::SectorValues{U1Irrep}, c::U1Irrep) = (n = twice(c.charge); 2*abs(n)+(n<=0))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The getindex definition just below the type definition provides the mechanism to get the concrete type as Irrep[G] for a given group G. Here, IrrepTable is the singleton type of which the constant Irrep is the only instance. The Base.convert definition allows to convert real numbers to the corresponding type of sector, and thus to omit the type information of the sector whenever this is clear from the context.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In the definition of U1Irrep, HalfInt<:Number is a Julia type defined in HalfIntegers.jl, which is also used for SU2Irrep below, that stores integer or half integer numbers using twice their value. Strictly speaking, the linear representations of U₁ can only have integer charges, and fractional charges lead to a projective representation. It can be useful to allow half integers in order to describe spin 1/2 systems with an axis rotation symmetry. As a user, you should not worry about the details of HalfInt, and additional methods for automatic conversion and pretty printing are provided, as illustrated by the following example","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Irrep[U₁](0.5)\nU1Irrep(0.4)\nU1Irrep(1) ⊗ Irrep[U₁](1//2)\nu = first(U1Irrep(1) ⊗ Irrep[U₁](1//2))\nNsymbol(u, conj(u), one(u))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For ZNIrrep{N}, we use an Int8 for compact storage, assuming that this type will not be used with N>64 (we need 2*(N-1) <= 127 in order for a ⊗ b to work correctly). We also define some aliases for the first (and most commonly used ℤ{N} irreps)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"const Z2Irrep = ZNIrrep{2}\nconst Z3Irrep = ZNIrrep{3}\nconst Z4Irrep = ZNIrrep{4}","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"so that we can do","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"z = Z3Irrep(1)\nZNIrrep{3}(1) ⊗ Irrep[ℤ₃](1)\nconj(z)\none(z)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As a further remark, even in the abelian case where a ⊗ b is equivalent to a single new label c, we return it as an iterable container, in this case a one-element tuple (c,).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned above, we also provide the following definitions","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.hash(c::ZNIrrep{N}, h::UInt) where {N} = hash(c.n, h)\nBase.isless(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = isless(c1.n, c2.n)\nBase.hash(c::U1Irrep, h::UInt) = hash(c.charge, h)\nBase.isless(c1::U1Irrep, c2::U1Irrep) where {N} =\n isless(abs(c1.charge), abs(c2.charge)) || zero(HalfInt) < c1.charge == -c2.charge","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Since sectors or objects made out of tuples of sectors (see the section on Fusion Trees below) are often used as keys in look-up tables (i.e. subtypes of AbstractDictionary in Julia), it is important that they can be hashed efficiently. We just hash the sectors above based on their numerical value. Note that hashes will only be used to compare sectors of the same type. The isless function provides a canonical order for sectors of a given type G<:Sector, which is useful to uniquely and unambiguously specify a representation space V = _a ℂ^n_a R_a, as described in the section on Graded spaces below.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first example of a non-abelian representation category is that of mathsfSU_2, the implementation of which is summarized by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SU2Irrep <: AbstractIrrep{SU{2}}\n j::HalfInt\nend\n\nBase.one(::Type{SU2Irrep}) = SU2Irrep(zero(HalfInt))\nBase.conj(s::SU2Irrep) = s\n⊗(s1::SU2Irrep, s2::SU2Irrep) = SectorSet{SU2Irrep}(abs(s1.j-s2.j):(s1.j+s2.j))\ndim(s::SU2Irrep) = twice(s.j)+1\nFusionStyle(::Type{SU2Irrep}) = SimpleFusion()\nBase.isreal(::Type{SU2Irrep}) = true\nNsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep) = WignerSymbols.δ(sa.j, sb.j, sc.j)\nFsymbol(s1::SU2Irrep, s2::SU2Irrep, s3::SU2Irrep,\n s4::SU2Irrep, s5::SU2Irrep, s6::SU2Irrep) =\n WignerSymbols.racahW(s1.j, s2.j, s4.j, s3.j, s5.j, s6.j)*sqrt(dim(s5)*dim(s6))\nfunction Rsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep)\n Nsymbol(sa, sb, sc) || return 0.\n iseven(convert(Int, sa.j+sb.j-sc.j)) ? 1.0 : -1.0\nend\n\nBase.IteratorSize(::Type{SectorValues{SU2Irrep}}) = IsInfinite()\nBase.iterate(::SectorValues{SU2Irrep}, i = 0) = (SU2Irrep(half(i)), i+1)\n# unused and not really necessary:\nBase.getindex(::SectorValues{SU2Irrep}, i::Int) =\n 1 <= i ? SU2Irrep(half(i-1)) : throw(BoundsError(values(SU2Irrep), i))\nfindindex(::SectorValues{SU2Irrep}, s::SU2Irrep) = twice(s.j)+1","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and some methods for pretty printing and converting from real numbers to irrep labels. As one can notice, the topological data (i.e. Nsymbol and Fsymbol) are provided by the package WignerSymbols.jl. The iterable a ⊗ b is a custom type, that the user does not need to care about. Some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = SU2Irrep(3//2)\nconj(s)\ndim(s)\ncollect(s ⊗ s)\nfor s2 in s ⊗ s\n @show s2\n @show Nsymbol(s, s, s2)\n @show Rsymbol(s, s, s2)\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"A final non-abelian representation theory is that of the semidirect product mathsfU₁ ℤ_2, where in the context of quantum systems, this occurs in the case of systems with particle hole symmetry and the non-trivial element of ℤ_2 acts as charge conjugation C. It has the effect of interchaning mathsfU_1 irreps n and -n, and turns them together in a joint 2-dimensional index, except for the case n=0. Irreps are therefore labeled by integers n 0, however for n=0 the ℤ₂ symmetry can be realized trivially or non-trivially, resulting in an even and odd one- dimensional irrep with mathsfU)_1 charge 0. Given mathsfU_1 mathsfSO_2, this group is also simply known as mathsfO_2, and the two representations with n = 0 are the scalar and pseudo-scalar, respectively. However, because we also allow for half integer representations, we refer to it as Irrep[CU₁] or CU1Irrep in full.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct CU1Irrep <: AbstractIrrep{CU₁}\n j::HalfInt # value of the U1 charge\n s::Int # rep of charge conjugation:\n # if j == 0, s = 0 (trivial) or s = 1 (non-trivial),\n # else s = 2 (two-dimensional representation)\n # Let constructor take the actual half integer value j\n function CU1Irrep(j::Real, s::Int = ifelse(j>zero(j), 2, 0))\n if ((j > zero(j) && s == 2) || (j == zero(j) && (s == 0 || s == 1)))\n new(j, s)\n else\n error(\"Not a valid CU₁ irrep\")\n end\n end\nend\n\nBase.one(::Type{CU1Irrep}) = CU1Irrep(zero(HalfInt), 0)\nBase.conj(c::CU1Irrep) = c\ndim(c::CU1Irrep) = ifelse(c.j == zero(HalfInt), 1, 2)\n\nFusionStyle(::Type{CU1Irrep}) = SimpleFusion()\n...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The rest of the implementation can be read in the source code, but is rather long due to all the different cases for the arguments of Fsymbol.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"So far, no sectors have been implemented with FusionStyle(G) == GenericFusion(), though an example would be the representation theory of mathsfSU_N, i.e. represented by the group SU{N}, for N>2. Such sectors are not yet fully supported; certain operations remain to be implemented. Furthermore, the topological data of the representation theory of such groups is not readily available and needs to be computed.","category":"page"},{"location":"man/sectors/#sss_productsectors","page":"Sectors, graded spaces and fusion trees","title":"Combining different sectors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"It is also possible to define two or more different types of symmetries, e.g. when the total symmetry group is a direct product of individual simple groups. Such sectors are obtained using the binary operator ⊠, which can be entered as \\boxtimes+TAB. First some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"a = Z3Irrep(1) ⊠ Irrep[U₁](1)\ntypeof(a)\nconj(a)\none(a)\ndim(a)\ncollect(a ⊗ a)\nFusionStyle(a)\nb = Irrep[ℤ₃](1) ⊠ Irrep[SU₂](3//2)\ntypeof(b)\nconj(b)\none(b)\ndim(b)\ncollect(b ⊗ b)\nFusionStyle(b)\nc = Irrep[SU₂](1) ⊠ SU2Irrep(3//2)\ntypeof(c)\nconj(c)\none(c)\ndim(c)\ncollect(c ⊗ c)\nFusionStyle(c)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We refer to the source file of ProductSector for implementation details.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The symbol ⊠ refers to the Deligne tensor product within the literature on category theory. Indeed, the category of representation of a product group G₁ × G₂ corresponds the Deligne tensor product of the categories of representations of the two groups separately. But this definition also extends to 𝕜-linear categories which are not the representation category of a group. Note that ⊠ also works in the type domain, i.e. Irrep[ℤ₃] ⊠ Irrep[CU₁] can be used to create ProductSector{Tuple{Irrep[ℤ₃], Irrep[CU₁]}}. Instances of this type can be constructed by giving a number of arguments, where the first argument is used to construct the first sector, and so forth. Furthermore, for representations of groups, we also enabled the notation Irrep[ℤ₃ × CU₁], with × obtained using \\times+TAB. However, this is merely for convience; as Irrep[ℤ₃] ⊠ Irrep[CU₁] is not a subtype of the abstract type AbstractIrrep{ℤ₃ × CU₁}. That behavior cannot be obtained with the Julia's type system. Some more examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"a = Z3Irrep(1) ⊠ Irrep[CU₁](1.5)\na isa Irrep[ℤ₃] ⊠ CU1Irrep\na isa Irrep[ℤ₃ × CU₁]\na isa Irrep{ℤ₃ × CU₁}\na == Irrep[ℤ₃ × CU₁](1, 1.5)","category":"page"},{"location":"man/sectors/#sss_newsectors","page":"Sectors, graded spaces and fusion trees","title":"Defining a new type of sector","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"By now, it should be clear how to implement a new Sector subtype. Ideally, a new I<:Sector type is a struct I ... end (immutable) that has isbitstype(I) == true (see Julia's manual), and implements the following minimal set of methods","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Base.one(::Type{I}) = I(...)\nBase.conj(a::I) = I(...)\nBase.isreal(::Type{I}) = ... # true or false\nTensorKit.FusionStyle(::Type{I}) = ... # UniqueFusion(), SimpleFusion(), GenericFusion()\nTensorKit.BraidingStyle(::Type{I}) = ... # Bosonic(), Fermionic(), Anyonic()\nTensorKit.Nsymbol(a::I, b::I, c::I) = ...\n # Bool or Integer if FusionStyle(I) == GenericFusion()\nBase.:⊗(a::I, b::I) = ... # some iterable object that generates all possible fusion outputs\nTensorKit.Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I)\nTensorKit.Rsymbol(a::I, b::I, c::I)\nBase.hash(a::I, h::UInt)\nBase.isless(a::I, b::I)\nBase.iterate(::TensorKit.SectorValues{I}[, state]) = ...\nBase.IteratorSize(::Type{TensorKit.SectorValues{I}}) = ... # HasLenght() or IsInfinite()\n# if previous function returns HasLength():\nBase.length(::TensorKit.SectorValues{I}) = ...\nBase.getindex(::TensorKit.SectorValues{I}, i::Int) = ...\nTensorKit.findindex(::TensorKit.SectorValues{I}, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Additionally, suitable definitions can be given for","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TensorKit.dim(a::I) = ...\nTensorKit.frobeniusschur(a::I) = ...\nTensorKit.Bsymbol(a::I, b::I, c::I) = ...","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Out of these, we have not yet encountered the Frobenius-Schur indicator and the B-symbol. They were both defined in the section on topological data of fusion categories and are fully determined by the F-symbol, just like the quantum dimensions. Hence, there is a default implementation for each of these three functions that just relies on Fsymbol, and alternative definitions need to be given only if a more efficient version is available.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If FusionStyle(I) == GenericFusion(), then the multiple outputs c in the tensor product of a and b will be labeled as i=1, 2, …, Nsymbol(a,b,c). Optionally, a different label can be provided by defining","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TensorKit.vertex_ind2label(i::Int, a::I, b::I, c::I) = ...\n# some label, e.g. a `Char` or `Symbol`","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The following function will then automatically determine the corresponding label type (which should not vary, i.e. vertex_ind2label should be type stable)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"vertex_labeltype(I::Type{<:Sector}) =\n typeof(vertex_ind2label(1, one(I), one(I), one(I)))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The following type, which already appeared in the implementation of SU2Irrep above, can be useful for providing the return type of a ⊗ b","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct SectorSet{I<:Sector,F,S}\n f::F\n set::S\nend\n...\nfunction Base.iterate(s::SectorSet{I}, args...) where {I<:Sector}\n next = iterate(s.set, args...)\n next === nothing && return nothing\n val, state = next\n return convert(I, s.f(val)), state\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"That is, SectorSet(f, set) behaves as an iterator that applies x->convert(I, f(x)) on the elements of set; if f is not provided it is just taken as the function identity.","category":"page"},{"location":"man/sectors/#sss_generalsectors","page":"Sectors, graded spaces and fusion trees","title":"Generalizations","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned before, the framework for sectors outlined above depends is in one-to-one correspondence to the topological data for specifying a unitary (spherical and braided, and hence ribbon) fusion category, which was reviewed at the end of the introduction to category theory. For such categories, the objects are not necessarily vector spaces and the fusion and splitting tensors X^ab_cμ do not necessarily exist as actual tensors. However, the morphism spaces c a b still behave as vector spaces, and the X^ab_cμ act as generic basis for that space. As TensorKit.jl does not rely on the X^ab_cμ themselves (even when they do exist) it can also deal with such general fusion categories. Note, though, that when X^ab_cμ does exist, it is available as fusiontensor(a,b,c[,μ]) (even though it is actually the splitting tensor) and can be useful for checking purposes, as illustrated below.","category":"page"},{"location":"man/sectors/#ss_rep","page":"Sectors, graded spaces and fusion trees","title":"Graded spaces","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We have introduced Sector subtypes as a way to label the irreps or sectors in the decomposition V = _a ℂ^n_a R_a. To actually represent such spaces, we now also introduce a corresponding type GradedSpace, which is a subtype of ElementarySpace{ℂ}, i.e.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct GradedSpace{I<:Sector, D} <: ElementarySpace{ℂ}\n dims::D\n dual::Bool\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Here, D is a type parameter to denote the data structure used to store the degeneracy or multiplicity dimensions n_a of the different sectors. For conviency, Vect[I] will return the fully concrete type with D specified.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that, conventionally, a graded vector space is a space that has a natural direct sum decomposition over some set of labels, i.e. V = _a I V_a where the label set I has the structure of a semigroup a b = c I. Here, we generalize this notation by using for I the fusion ring of a fusion category, a b = _c I _μ = 1^N_ab^c c. However, this is mostly to lower the barrier, as really the instances of GradedSpace represent just general objects in a fusion category (or strictly speaking, a pre-fusion category, as we allow for an infinite number of simple objects, e.g. the irreps of a continuous group).","category":"page"},{"location":"man/sectors/#Implementation-details","page":"Sectors, graded spaces and fusion trees","title":"Implementation details","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned, the way in which the degeneracy dimensions n_a are stored depends on the specific sector type I, more specifically on the IteratorSize of values(I). If IteratorSize(values(I)) isa Union{IsInfinite, SizeUnknown}, the different sectors a and their corresponding degeneracy n_a are stored as key value pairs in an Associative array, i.e. a dictionary dims::SectorDict. As the total number of sectors in values(I) can be infinite, only sectors a for which n_a are stored. Here, SectorDict is a constant type alias for a specific dictionary implementation, which currently resorts to SortedVectorDict implemented in TensorKit.jl. Hence, the sectors and their corresponding dimensions are stored as two matching lists (Vector instances), which are ordered based on the property isless(a::I, b::I). This ensures that the space V = _a ℂ^n_a R_a has some unique canonical order in the direct sum decomposition, such that two different but equal instances created independently always match.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"If IteratorSize(values(I)) isa Union{HasLength, HasShape}, the degeneracy dimensions n_a are stored for all sectors a ∈ values(I) (also if n_a == 0) in a tuple, more specifically a NTuple{N, Int} with N = length(values(I)). The methods getindex(values(I), i) and findindex(values(I), a) are used to map between a sector a ∈ values(I) and a corresponding index i ∈ 1:N. As N is a compile time constant, these types can be created in a type stable manner.","category":"page"},{"location":"man/sectors/#Constructing-instances","page":"Sectors, graded spaces and fusion trees","title":"Constructing instances","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As mentioned, the convenience method Vect[I] will return the concrete type GradedSpace{I,D} with the matching value of D, so that should never be a user's concern. In fact, for consistency, Vect[Trivial] will just return ComplexSpace, which is not even a specific type of GradedSpace. For the specific case of group irreps as sectors, one can use Rep[G] with G the group, as inspired by the categorical name mathbfRep_mathsfG. Some illustrations:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Vect[Trivial]\nVect[U1Irrep]\nVect[Irrep[U₁]]\nRep[U₁]\nRep[ℤ₂ × SU₂]\nVect[Irrep[ℤ₂ × SU₂]]","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that we also have the specific alias U₁Space. In fact, for all the common groups we have a number of alias, both in ASCII and using Unicode:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"# ASCII type aliases\nconst ZNSpace{N} = GradedSpace{ZNIrrep{N}, NTuple{N,Int}}\nconst Z2Space = ZNSpace{2}\nconst Z3Space = ZNSpace{3}\nconst Z4Space = ZNSpace{4}\nconst U1Space = Rep[U₁]\nconst CU1Space = Rep[CU₁]\nconst SU2Space = Rep[SU₂]\n\n# Unicode alternatives\nconst ℤ₂Space = Z2Space\nconst ℤ₃Space = Z3Space\nconst ℤ₄Space = Z4Space\nconst U₁Space = U1Space\nconst CU₁Space = CU1Space\nconst SU₂Space = SU2Space","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"To create specific instances of those types, one can e.g. just use V = GradedSpace(a=>n_a, b=>n_b, c=>n_c) or V = GradedSpace(iterator) where iterator is any iterator (e.g. a dictionary or a generator) that yields Pair{I,Int} instances. With those constructions, I is inferred from the type of sectors. However, it is often more convenient to specify the sector type explicitly (using one of the many alias provided), since then the sectors are automatically converted to the correct type. Thereto, one can use Vect[I], or when I corresponds to the irreducible representations of a group, Rep[G]. Some examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Vect[Irrep[U₁]](0=>3, 1=>2, -1=>1) ==\n GradedSpace(U1Irrep(0)=>3, U1Irrep(1)=>2, U1Irrep(-1)=>1) == \n U1Space(0=>3, 1=>2, -1=>1)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The fact that Rep[G] also works with product groups makes it easy to specify e.g.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Rep[ℤ₂ × SU₂]((0,0) => 3, (1,1/2) => 2, (0,1) => 1) == \n GradedSpace((Z2Irrep(0) ⊠ SU2Irrep(0)) => 3, (Z2Irrep(1) ⊠ SU2Irrep(1/2)) => 2, (Z2Irrep(0) ⊠ SU2Irrep(1)) => 1)","category":"page"},{"location":"man/sectors/#Methods","page":"Sectors, graded spaces and fusion trees","title":"Methods","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There are a number of methods to work with instances V of GradedSpace. The function sectortype returns the type of the sector labels. It also works on other vector spaces, in which case it returns Trivial. The function sectors returns an iterator over the different sectors a with non-zero n_a, for other ElementarySpace types it returns (Trivial,). The degeneracy dimensions n_a can be extracted as dim(V, a), it properly returns 0 if sector a is not present in the decomposition of V. With hassector(V, a) one can check if V contains a sector a with dim(V,a)>0. Finally, dim(V) returns the total dimension of the space V, i.e. _a n_a d_a or thus dim(V) = sum(dim(V,a) * dim(a) for a in sectors(V)). Note that a representation space V has certain sectors a with dimensions n_a, then its dual V' will report to have sectors dual(a), and dim(V', dual(a)) == n_a. There is a subtelty regarding the difference between the dual of a representation space R_a^*, on which the conjugate representation acts, and the representation space of the irrep dual(a)==conj(a) that is isomorphic to the conjugate representation, i.e. R_overlinea R_a^* but they are not equal. We return to this in the section on fusion trees. This is true also in more general fusion categories beyond the representation categories of groups.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Other methods for ElementarySpace, such as dual, fuse and flip also work. In fact, GradedSpace is the reason flip exists, cause in this case it is different then dual. The existence of flip originates from the non-trivial isomorphism between R_overlinea and R_a^*, i.e. the representation space of the dual overlinea of sector a and the dual of the representation space of sector a. In order for flip(V) to be isomorphic to V, it is such that, if V = GradedSpace(a=>n_a,...) then flip(V) = dual(GradedSpace(dual(a)=>n_a,....)).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Furthermore, for two spaces V1 = GradedSpace(a=>n1_a, ...) and V2 = GradedSpace(a=>n2_a, ...), we have infimum(V1,V2) = GradedSpace(a=>min(n1_a,n2_a), ....) and similarly for supremum, i.e. they act on the degeneracy dimensions of every sector separately. Therefore, it can be that the return value of infimum(V1,V2) or supremum(V1,V2) is neither equal to V1 or V2.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For W a ProductSpace{Vect[I], N}, sectors(W) returns an iterator that generates all possible combinations of sectors as represented as NTuple{I,N}. The function dims(W, as) returns the corresponding tuple with degeneracy dimensions, while dim(W, as) returns the product of these dimensions. hassector(W, as) is equivalent to dim(W, as)>0. Finally, there is the function blocksectors(W) which returns a list (of type Vector) with all possible \"block sectors\" or total/coupled sectors that can result from fusing the individual uncoupled sectors in W. Correspondingly, blockdim(W, a) counts the total degeneracy dimension of the coupled sector a in W. The machinery for computing this is the topic of the next section on Fusion trees, but first, it's time for some examples.","category":"page"},{"location":"man/sectors/#Examples","page":"Sectors, graded spaces and fusion trees","title":"Examples","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Let's start with an example involving mathsfU_1:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"V1 = Rep[U₁](0=>3, 1=>2, -1=>1)\nV1 == U1Space(0=>3, 1=>2, -1=>1) == U₁Space(-1=>1, 1=>2,0=>3) # order doesn't matter\n(sectors(V1)...,)\ndim(V1, U1Irrep(1))\ndim(V1', Irrep[U₁](1)) == dim(V1, conj(U1Irrep(1))) == dim(V1, U1Irrep(-1))\nhassector(V1, Irrep[U₁](1))\nhassector(V1, Irrep[U₁](2))\ndual(V1)\nflip(V1)\ndual(V1) ≅ V1\nflip(V1) ≅ V1\nV2 = U1Space(0=>2, 1=>1, -1=>1, 2=>1, -2=>1)\ninfimum(V1, V2)\nsupremum(V1, V2)\n⊕(V1,V2)\nW = ⊗(V1,V2)\ncollect(sectors(W))\ndims(W, (Irrep[U₁](0), Irrep[U₁](0)))\ndim(W, (Irrep[U₁](0), Irrep[U₁](0)))\nhassector(W, (Irrep[U₁](0), Irrep[U₁](0)))\nhassector(W, (Irrep[U₁](2), Irrep[U₁](0)))\nfuse(W)\n(blocksectors(W)...,)\nblockdim(W, Irrep[U₁](0))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and then with mathsfSU_2:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"V1 = Vect[Irrep[SU₂]](0=>3, 1//2=>2, 1=>1)\nV1 == SU2Space(0=>3, 1/2=>2, 1=>1) == SU₂Space(0=>3, 0.5=>2, 1=>1)\n(sectors(V1)...,)\ndim(V1, SU2Irrep(1))\ndim(V1', SU2Irrep(1)) == dim(V1, conj(SU2Irrep(1))) == dim(V1, Irrep[SU₂](1))\ndim(V1)\nhassector(V1, Irrep[SU₂](1))\nhassector(V1, Irrep[SU₂](2))\ndual(V1)\nflip(V1)\nV2 = SU2Space(0=>2, 1//2=>1, 1=>1, 3//2=>1, 2=>1)\ninfimum(V1, V2)\nsupremum(V1, V2)\n⊕(V1,V2)\nW = ⊗(V1,V2)\ncollect(sectors(W))\ndims(W, (Irrep[SU₂](0), Irrep[SU₂](0)))\ndim(W, (Irrep[SU₂](0), Irrep[SU₂](0)))\nhassector(W, (SU2Irrep(0), SU2Irrep(0)))\nhassector(W, (SU2Irrep(2), SU2Irrep(0)))\nfuse(W)\n(blocksectors(W)...,)\nblockdim(W, SU2Irrep(0))","category":"page"},{"location":"man/sectors/#ss_fusiontrees","page":"Sectors, graded spaces and fusion trees","title":"Fusion trees","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The gain in efficiency (both in memory occupation and computation time) obtained from using symmetric (equivariant) tensor maps is that, by Schur's lemma, they are block diagonal in the basis of coupled sectors, i.e. they exhibit block sparsity. To exploit this block diagonal form, it is however essential that we know the basis transform from the individual (uncoupled) sectors appearing in the tensor product form of the domain and codomain, to the totally coupled sectors that label the different blocks. We refer to the latter as block sectors, as we already encountered in the previous section blocksectors and blockdim defined on the type ProductSpace.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"This basis transform consists of a basis of inclusion and projection maps, denoted as X^a_1a_2a_N_cα R_c R_a_1 R_a_2 R_a_N and their adjoints (X^a_1a_2a_N_cα)^, such that","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(X^a_1a_2a_N_cα)^ X^a_1a_2a_N_cα = δ_cc δ_αα mathrmid_c","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"and","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"_cα X^a_1a_2a_N_cα (X^a_1a_2a_N_cα)^ = mathrmid_a_1 a_2 a_N = mathrmid_a_1 mathrmid_a_2 mathrmid_a_N","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Fusion trees provide a particular way to construct such a basis. It is useful to know about the existence of fusion trees and how they are represented, as discussed in the first subsection. The next two subsections discuss possible manipulations that can be performed with fusion trees. These are used under the hood when manipulating the indices of tensors, but a typical user would not need to use these manipulations on fusion trees directly. Hence, these last two sections can safely be skipped.","category":"page"},{"location":"man/sectors/#Canonical-representation","page":"Sectors, graded spaces and fusion trees","title":"Canonical representation","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"To couple or fuse the different sectors together into a single block sector, we can sequentially fuse together two sectors into a single coupled sector, which is then fused with the next uncoupled sector, using the splitting tensors X_ab^cμ R_c R_a R_b and their adjoints. This amounts to the canonical choice of our tensor product, and for a given tensor mapping from (((W_1 W_2) W_3) ) W_N_2) to (((V_1 V_2) V_3) ) V_N_1), the corresponding fusion and splitting trees take the form","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: double fusion tree)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"for the specific case N_1=4 and N_2=3. We can separate this tree into the fusing part (b_1b_2)b_3 c and the splitting part c(((a_1a_2)a_3)a_4). Given that the fusion tree can be considered to be the adjoint of a corresponding splitting tree c(b_1b_2)b_3, we now first consider splitting trees in isolation. A splitting tree which goes from one coupled sectors c to N uncoupled sectors a_1, a_2, …, a_N needs N-2 additional internal sector labels e_1, …, e_N-2, and, if FusionStyle(I) isa GenericFusion, N-1 additional multiplicity labels μ_1, …, μ_N-1. We henceforth refer to them as vertex labels, as they are associated with the vertices of the splitting tree. In the case of FusionStyle(I) isa UniqueFusion, the internal sectors e_1, …, e_N-2 are completely fixed, for FusionStyle(I) isa MultipleFusion they can also take different values. In our abstract notation of the splitting basis X^a_1a_2a_N_cα used above, α can be consided a collective label, i.e. α = (e_1 e_N-2 μ₁ μ_N-1). Indeed, we can check the orthogonality condition (X^a_1a_2a_N_cα)^ X^a_1a_2a_N_cα = δ_cc δ_αα mathrmid_c, which now forces all internal lines e_k and vertex labels μ_l to be the same.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is one subtle remark that we have so far ignored. Within the specific subtypes of Sector, we do not explicitly distinguish between R_a^* (simply denoted as a^* and graphically depicted as an upgoing arrow a) and R_bara (simply denoted as bara and depicted with a downgoing arrow), i.e. between the dual space of R_a on which the conjugated irrep acts, or the irrep bara to which the complex conjugate of irrep a is isomorphic. This distinction is however important, when certain uncoupled sectors in the fusion tree actually originate from a dual space. We use the isomorphisms Z_aR_a^* R_bara and its adjoint Z_a^R_baraR_a^*, as introduced in the section on topological data of a fusion category, to build fusion and splitting trees that take the distinction between irreps and their conjugates into account. Hence, in the previous example, if e.g. the first and third space in the codomain and the second space in the domain of the tensor were dual spaces, the actual pair of splitting and fusion tree would look as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: extended double fusion tree)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The presence of these isomorphisms will be important when we start to bend lines, to move uncoupled sectors from the incoming to the outgoing part of the fusion-splitting tree. Note that we can still represent the fusion tree as the adjoint of a corresponding splitting tree, because we also use the adjoint of the Z isomorphisms in the splitting part, and the Z isomorphism in the fusion part. Furthermore, the presence of the Z isomorphisms does not affect the orthonormality.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We represent splitting trees and their adjoints using a specific immutable type called FusionTree (which actually represents a splitting tree, but fusion tree is a more common term), defined as","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"struct FusionTree{I<:Sector,N,M,L,T}\n uncoupled::NTuple{N,I}\n coupled::I\n isdual::NTuple{N,Bool}\n innerlines::NTuple{M,I} # fixed to M = N-2\n vertices::NTuple{L,T} # fixed to L = N-1\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Here, the fields are probably self-explanotary. The isdual field indicates whether an isomorphism is present (if the corresponding value is true) or not. Note that the field uncoupled contains the sectors coming out of the splitting trees, before the possible Z isomorphism, i.e. the splitting tree in the above example would have sectors = (a₁, a₂, a₃, a₄). The FusionTree type has a number of basic properties and capabilities, such as checking for equality with == and support for hash(f::FusionTree, h::UInt), as splitting and fusion trees are used as keys in look-up tables (i.e. AbstractDictionary instances) to look up certain parts of the data of a tensor. The type of L of the vertex labels can be Nothing when they are not needed (i.e. if FusionStyle(I) isa MultiplicityFreeFusion).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"FusionTree instances are not checked for consistency (i.e. valid fusion rules etc) upon creation, hence, they are assumed to be created correctly. The most natural way to create them is by using the fusiontrees(uncoupled::NTuple{N,I}, coupled::I = one(I)) method, which returns an iterator over all possible fusion trees from a set of N uncoupled sectors to a given coupled sector, which by default is assumed to be the trivial sector of that group or fusion category (i.e. the identity object in categorical nomenclature). The return type of fusiontrees is a custom type FusionTreeIterator which conforms to the complete interface of an iterator, and has a custom length function that computes the number of possible fusion trees without iterating over all of them explicitly. This is best illustrated with some examples","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = Irrep[SU₂](1/2)\ncollect(fusiontrees((s,s,s,s)))\ncollect(fusiontrees((s,s,s,s,s), s, (true, false, false, true, false)))\niter = fusiontrees(ntuple(n->s, 16))\nsum(n->1, iter)\nlength(iter)\n@elapsed sum(n->1, iter)\n@elapsed length(iter)\ns2 = s ⊠ s\ncollect(fusiontrees((s2,s2,s2,s2)))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that FusionTree instances are shown (printed) in a way that is valid code to reproduce them, a property which also holds for both instances of Sector and instances of VectorSpace. All of those should be displayed in a way that can be copy pasted as valid code. Furthermore, we use context to determine how to print e.g. a sector. In isolation, s2 is printed as (Irrep[SU₂](1/2) ⊠ Irrep[SU₂](1/2)), however, within the fusion tree, it is simply printed as (1/2, 1/2), because it will be converted back into a ProductSector, namely Irrep[SU₂] ⊠ Irrep[SU₂] by the constructor of FusionTree{Irrep[SU₂] ⊠ Irrep[SU₂]}.","category":"page"},{"location":"man/sectors/#Manipulations-on-a-fusion-tree","page":"Sectors, graded spaces and fusion trees","title":"Manipulations on a fusion tree","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We now discuss elementary manipulations that we want to perform on or between fusion trees (where we actually mean splitting trees), which will form the building block for more general manipulations on a pair of a fusion and splitting tree discussed in the next subsection, and then for casting a general index manipulation of a tensor map as a linear operation in the basis of canonically ordered splitting and fusion trees. In this section, we will ignore the Z isomorphisms, as they are just trivially reshuffled under the different operations that we describe. These manipulations are used as low-level methods by the TensorMap methods discussed on the next page. As such, they are not exported by TensorKit.jl, nor do they overload similarly named methods from Julia Base (see split and merge below).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The first operation we discuss is an elementary braid of two neighbouring sectors (indices), i.e. a so-called Artin braid or Artin generator of the braid group. Because these two sectors do not appear on the same fusion vertex, some recoupling is necessary. The following represents two different ways to compute the result of such a braid as a linear combination of new fusion trees in canonical order:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: artin braid)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"While the upper path is the most intuitive, it requires two recouplings or F-moves (one forward and one reverse). On the other hand, the lower path requires only one (reverse) F- move, and two R-moves. The latter are less expensive to compute, and so the lower path is computationally more efficient. However, the end result should be the same, provided the pentagon and hexagon equations are satisfied. We always assume that these are satisfied for any new subtype of Sector, and it is up to the user to verify that they are when implementing new custom Sector types. This result is implemented in the function artin_braid(f::FusionTree, i; inv = false) where i denotes the position of the first sector (i.e. labeled b in the above graph) which is then braided with the sector at position i+1 in the fusion tree f. The keyword argument inv allows to select the inverse braiding operation, which amounts to replacing the R-matrix with its inverse (or thus, adjoint) in the above steps. The result is returned as a dictionary with possible output fusion trees as keys and corresponding coefficients as value. In the case of FusionStyle(I) isa UniqueFusion, their is only one resulting fusion tree, with corresponding coefficient a complex phase (which is one for the bosonic representation theory of an Abelian group), and the result is a special SingletonDict<:AbstractDict, a struct type defined in TensorKit.jl to hold a single key value pair.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With the elementary artin_braid, we can then compute a more general braid. For this, we provide an interface","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"braid(f::FusionTree{I,N}, levels::NTuple{N,Int}, permutation::NTuple{N,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where the braid is specified as a permutation, such that the new sector at position i was originally at position permutation[i], and where every uncoupled sector is also assigned a level or depth. The permutation is decomposed into swaps between neighbouring sectors, and when two sectors are swapped, their respective level will determine whether the left sector is braided over or under its right neighbor. This interface does not allow to specify the most general braid, and in particular will never wind one line around another, but can be used as a more general building block for arbitrary braids than the elementary Artin generators. A graphical example makes this probably more clear, i.e for levels=(1,2,3,4,5) and permutation=(5,3,1,4,2), the corresponding braid is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: braid interface)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"that is, the first sector or space goes to position 3, and crosses over all other lines, because it has the lowest level (i.e. think of level as depth in the third dimension), and so forth. We sketch this operation both as a general braid on the left hand side, and as a particular composition of Artin braids on the right hand side.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"When BraidingStyle(I) == SymmetricBraiding(), there is no distinction between applying the braiding or its inverse (i.e. lines crossing over or under each other in the graphical notation) and the whole operation simplifies down to a permutation. We then also support the interface","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"permute(f::FusionTree{I,N}, permutation::NTuple{N,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Other manipulations which are sometimes needed are","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"insertat(f1::FusionTree{I,N₁}, i::Int, f2::FusionTree{I,N₂}) : inserts a fusion tree f2 at the ith uncoupled sector of fusion tree f1 (this requires that the coupled sector f2 matches with the ith uncoupled sector of f1, and that !f1.isdual[i], i.e. that there is no Z-isomorphism on the ith line of f1), and recouple this into a linear combination of trees in canonical order, with N₁+N₂-1 uncoupled sectors, i.e. diagrammatically for i=3\n(Image: insertat)\nsplit(f::FusionTree{I,N}, M::Int) : splits a fusion tree f into two trees f1 and f2, such that f1 has the first M uncoupled sectors of f, and f2 the remaining N-M. This function is type stable if M is a compile time constant.\nsplit(f, M) is the inverse of insertat in the sence that insertat(f2, 1, f1) should return a dictionary with a single key-value pair f=>1. Diagrammatically, for M=4, the function split returns\n(Image: split)\nmerge(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, c::I, μ=nothing) : merges two fusion trees f1 and f2 by fusing the coupled sectors of f1 and f2 into a sector c (with vertex label μ if FusionStyle(I) == GenericFusion()), and reexpressing the result as a linear combination of fusion trees with N₁+N₂ uncoupled sectors in canonical order. This is a simple application of insertat. Diagrammatically, this operation is represented as:\n(Image: merge)","category":"page"},{"location":"man/sectors/#Manipulations-on-a-splitting-fusion-tree-pair","page":"Sectors, graded spaces and fusion trees","title":"Manipulations on a splitting - fusion tree pair","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"In this subsection we discuss manipulations that act on a splitting and fusion tree pair, which we will always as two separate trees f1, f2, where f1 is the splitting tree and f2 represents the fusion tree, and they should have f1.coupled == f2.coupled.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The most important manipulation on such a pair is to move sectors from one to the other. Given the canonical order of these trees, we exclusively use the left duality (see the section on categories), for which the evaluation and coevaluation maps establish isomorphisms between","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"mathrmHom((((b_1 b_2) ) b_N_2) (((a_1 a_2) ) a_N_1))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":" mathrmHom((((b_1 b_2) ) b_N_2-1) ((((a_1 a_2) ) a_N_1) b_N_2^*))","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":" mathrmHom(1 (((((((a_1 a_2) ) a_N_1) b_N_2^*) ) b_2^*) b_1^*) )","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where the last morphism space is then labeled by the basis of only splitting trees. We can then use the manipulations from the previous section, and then again use the left duality to bring this back to a pair of splitting and fusion tree with N₂′ incoming and N₁′ incoming sectors (with N₁′ + N₂′ == N₁ + N₂).","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We now discuss how to actually bend lines, and thus, move sectors from the incoming part (fusion tree) to the outgoing part (splitting tree). Hereby, we exploit the relations between the (co)evaluation (exact pairing) and the fusion tensors, discussed in topological data of a fusion category. The main ingredient that we need is summarized in","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: line bending)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"We will only need the B-symbol and not the A-symbol. Applying the left evaluation on the second sector of a splitting tensor thus yields a linear combination of fusion tensors (when FusionStyle(I) == GenericFusion(), or just a scalar times the corresponding fusion tensor otherwise), with corresponding Z ismorphism. Taking the adjoint of this relation yields the required relation to transform a fusion tensor into a splitting tensor with an added Z^ isomorphism.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"However, we have to be careful if we bend a line on which a Z isomorphism (or its adjoint) is already present. Indeed, it is exactly for this operation that we explicitly need to take the presence of these isomorphisms into account. Indeed, we obtain the relation","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: dual line bending)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Hence, bending an isdual sector from the splitting tree to the fusion tree yields an additional Frobenius-Schur factor, and of course leads to a normal sector (which is no longer isdual and does thus not come with a Z-isomorphism) on the fusion side. We again use the adjoint of this relation to bend an isdual sector from the fusion tree to the splitting tree.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The FusionTree interface to duality and line bending is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"repartition(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, N::Int)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"which takes a splitting tree f1 with N₁ outgoing sectors, a fusion tree f2 with N₂ incoming sectors, and applies line bending such that the resulting splitting and fusion trees have N outgoing sectors, corresponding to the first N sectors out of the list (a_1 a_2 a_N_1 b_N_2^* b_1^*) and N₁+N₂-N incoming sectors, corresponding to the dual of the last N₁+N₂-N sectors from the previous list, in reverse. This return values are correctly inferred if N is a compile time constant.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Graphically, for N₁ = 4, N₂ = 3, N = 2 and some particular choice of isdual in both the fusion and splitting tree:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"(Image: repartition)","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The result is returned as a dictionary with keys (f1′, f2′) and the corresponding coeff as value. Note that the summation is only over the κ_j labels, such that, in the case of FusionStyle(I) isa MultiplicityFreeFusion, the linear combination simplifies to a single term with a scalar coefficient.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"With this basic function, we can now perform arbitrary combinations of braids or permutations with line bendings, to completely reshuffle where sectors appear. The interface provided for this is given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"braid(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, levels1::NTuple{N₁,Int}, levels2::NTuple{N₂,Int}, p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"where we now have splitting tree f1 with N₁ outgoing sectors, a fusion tree f2 with N₂ incoming sectors, levels1 and levels2 assign a level or depth to the corresponding uncoupled sectors in f1 and f2, and we represent the new configuration as a pair p1 and p2. Together, (p1..., p2...) represents a permutation of length N₁+N₂ = N₁′+N₂′, where p1 indicates which of the original sectors should appear as outgoing sectors in the new splitting tree and p2 indicates which appear as incoming sectors in the new fusion tree. Hereto, we label the uncoupled sectors of f1 from 1 to N₁, followed by the uncoupled sectors of f2 from N₁+1 to N₁+N₂. Note that simply repartitioning the splitting and fusion tree such that e.g. all sectors appear in the new splitting tree (i.e. are outgoing), amounts to chosing p1 = (1,..., N₁, N₁+N₂, N₁+N₂-1, ... , N₁+1) and p2=(), because the duality isomorphism reverses the order of the tensor product.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"This routine is implemented by indeed first making all sectors outgoing using the repartition function discussed above, such that only splitting trees remain, then braiding those using the routine from the previous subsection such that the new outgoing sectors appear first, followed by the new incoming sectors (in reverse order), and then again invoking the repartition routine to bring everything in final form. The result is again returned as a dictionary where the keys are (f1′,f2′) and the values the corresponding coefficients.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"As before, there is a simplified interface for the case where BraidingStyle(I) isa SymmetricBraiding and the levels are not needed. This is simply given by","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"permute(f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}, p1::NTuple{N₁′,Int}, p2::NTuple{N₂′,Int})","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The braid and permute routines for double fusion trees will be the main access point for corresponding manipulations on tensors. As a consequence, results from this routine are memoized, i.e. they are stored in some package wide 'least-recently used' cache (from LRUCache.jl) that can be accessed as TensorKit.braidcache. By default, this cache stores up to 10^5 different braid or permute resuls, where one result corresponds to one particular combination of (f1, f2, p1, p2, levels1, levels2). This should be sufficient for most algorithms. While there are currently no (official) access methods to change the default settings of this cache (one can always resort to resize!(TensorKit.permutecache) and other methods from LRUCache.jl), this might change in the future. The use of this cache is however controlled by two constants of type RefValue{Bool}, namely usebraidcache_abelian and usebraidcache_nonabelian. The default values are given by TensorKit.usebraidcache_abelian[] = false and TensorKit.usebraidcache_nonabelian[] = true, and respectively reflect that the cache is likely not going to help (or even slow down) fusion trees with FusionStyle(f) isa UniqueFusion, but is probably useful for fusion trees with FusionStyle(f) isa MultipleFusion. One can change these values and test the effect on their application.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"The existence of braidcache also implies that potential inefficiencies in the fusion tree manipulations (which we nonetheless try to avoid) will not seriously affect performance of tensor manipulations.","category":"page"},{"location":"man/sectors/#Inspecting-fusion-trees-as-tensors","page":"Sectors, graded spaces and fusion trees","title":"Inspecting fusion trees as tensors","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"For those cases where the fusion and splitting tensors have an explicit representation as a tensor, i.e. a morphism in the category Vect (this essentially coincides with the case of group representations), this explicit representation can be created, which can be useful for checking purposes. Hereto, it is necessary that the splitting tensor X^ab_cμ, i.e. the Clebsch-Gordan coefficients of the group, are encoded via the routine fusiontensor(a,b,c [,μ = nothing]), where the last argument is only necessary in the case of FusionStyle(I) == GenericFusion(). We can then convert a FusionTree{I,N} into an Array, which will yield a rank N+1 array where the first N dimensions correspond to the uncoupled sectors, and the last dimension to the coupled sector. Note that this is mostly useful for the case of FusionStyle(I) isa MultipleFusion groups, as in the case of abelian groups, all irreps are one-dimensional.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Some examples:","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"s = Irrep[SU₂](1/2)\niter = fusiontrees((s, s, s, s), SU2Irrep(1))\nf = first(iter)\nconvert(Array, f)\n\nI ≈ convert(Array, FusionTree((SU₂(1/2),), SU₂(1/2), (false,), ()))\nZ = adjoint(convert(Array, FusionTree((SU2Irrep(1/2),), SU2Irrep(1/2), (true,), ())))\ntranspose(Z) ≈ frobeniusschur(SU2Irrep(1/2)) * Z\n\nI ≈ convert(Array, FusionTree((Irrep[SU₂](1),), Irrep[SU₂](1), (false,), ()))\nZ = adjoint(convert(Array, FusionTree((Irrep[SU₂](1),), Irrep[SU₂](1), (true,), ())))\ntranspose(Z) ≈ frobeniusschur(Irrep[SU₂](1)) * Z\n\n#check orthogonality\nfor f1 in iter\n for f2 in iter\n dotproduct = dot(convert(Array, f1), convert(Array, f2))\n println(\"< $f1, $f2> = $dotproduct\")\n end\nend","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Note that we take the adjoint when computing Z, because convert(Array, f) assumes f to be splitting tree, which is built using Z^. Further note that the normalization (squared) of a fusion tree is given by the dimension of the coupled sector, as we are also tracing over the mathrmid_c when checking the orthogonality by computing dot of the corresponding tensors.","category":"page"},{"location":"man/sectors/#Fermions","page":"Sectors, graded spaces and fusion trees","title":"Fermions","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"TODO: Update the documentation for this section.","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"Fermionic sectors are represented by the type FermionParity, which effectively behaves like a ℤ₂ sector, but with two modifications. Firstly, the exchange of two sectors with odd fermion parity should yield a minus sign, which is taken care of by virtue of the R-symbol. This ensures that permuting tensors behave as expected. Secondly, diagrams with self-crossing lines (aka twists) give rise to a minus sign for odd fermion parity. This is in essence equivalent to having supertraces, which is what ensures that @tensor has a result that is invariant under permutation of its input tensors. This does however lead to unwanted minus signs for certain types of diagrams. To avoid this, the @planar macro does not include a supertrace, but requires a manual resolution of all crossings in the diagram.","category":"page"},{"location":"man/sectors/#Anyons","page":"Sectors, graded spaces and fusion trees","title":"Anyons","text":"","category":"section"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"There is currently one example of a Sector subtype that has anyonic braiding style, namely that of the Fibonacci fusion category. It has to (isomorphism classes of) simple objects, namely the identity 𝟙 and a non-trivial object known as τ, with fusion rules τ ⊗ τ = 𝟙 ⊕ τ. Let's summarize the topological data","category":"page"},{"location":"man/sectors/","page":"Sectors, graded spaces and fusion trees","title":"Sectors, graded spaces and fusion trees","text":"𝟙 = FibonacciAnyon(:I)\nτ = FibonacciAnyon(:τ)\ncollect(τ ⊗ τ)\nFusionStyle(τ)\nBraidingStyle(τ)\ndim(𝟙)\ndim(τ)\nF𝟙 = Fsymbol(τ,τ,τ,𝟙,τ,τ)\nFτ = [Fsymbol(τ,τ,τ,τ,𝟙,𝟙) Fsymbol(τ,τ,τ,τ,𝟙,τ); Fsymbol(τ,τ,τ,τ,τ,𝟙) Fsymbol(τ,τ,τ,τ,τ,τ)]\nFτ'*Fτ\npolar(x) = rationalize.((abs(x), angle(x)/(2pi)))\nRsymbol(τ,τ,𝟙) |> polar\nRsymbol(τ,τ,τ) |> polar\ntwist(τ) |> polar","category":"page"},{"location":"index/#Index","page":"Index","title":"Index","text":"","category":"section"},{"location":"index/","page":"Index","title":"Index","text":"","category":"page"},{"location":"man/tutorial/#s_tutorial","page":"Tutorial","title":"Tutorial","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Before discussing at length all aspects of this package, both its usage and implementation, we start with a short tutorial to sketch the main capabilities. Thereto, we start by loading TensorKit.jl","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"using TensorKit","category":"page"},{"location":"man/tutorial/#Cartesian-tensors","page":"Tutorial","title":"Cartesian tensors","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"The most important objects in TensorKit.jl are tensors, which we now create with random (normally distributed) entries in the following manner","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that we entered the tensor size not as plain dimensions, by specifying the vector space associated with these tensor indices, in this case ℝ^n, which can be obtained by typing \\bbR+TAB. The tensor then lives in the tensor product of the different spaces, which we can obtain by typing ⊗ (i.e. \\otimes+TAB), although for simplicity also the usual multiplication sign * does the job. Note also that A is printed as an instance of a parametric type TensorMap, which we will discuss below and contains Tensor.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Let us briefly sidetrack into the nature of ℝ^n:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = ℝ^3\ntypeof(V)\nV == CartesianSpace(3)\nsupertype(CartesianSpace)\nsupertype(ElementarySpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"i.e. ℝ^n can also be created without Unicode using the longer syntax CartesianSpace(n). It is subtype of ElementarySpace, with a standard (Euclidean) inner product over the real numbers. Furthermore,","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"W = ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4\ntypeof(W)\nsupertype(ProductSpace)\nsupertype(CompositeSpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"i.e. the tensor product of a number of CartesianSpaces is some generic parametric ProductSpace type, specifically ProductSpace{CartesianSpace,N} for the tensor product of N instances of CartesianSpace.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Tensors are itself vectors (but not Vectors or even AbstractArrays), so we can compute linear combinations, provided they live in the same space.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(ℝ^3 * ℝ^2 * ℝ^4);\nC = 0.5*A + 2.5*B","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Given that they are behave as vectors, they also have a scalar product and norm, which they inherit from the Euclidean inner product on the individual ℝ^n spaces:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"scalarBA = dot(B,A)\nscalarAA = dot(A,A)\nnormA² = norm(A)^2","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"More generally, our tensor objects implement the full interface layed out in VectorInterface.jl.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"If two tensors live on different spaces, these operations have no meaning and are thus not allowed","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B′ = randn(ℝ^4 * ℝ^2 * ℝ^3);\nspace(B′) == space(A)\nC′ = 0.5*A + 2.5*B′\nscalarBA′ = dot(B′,A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"However, in this particular case, we can reorder the indices of B′ to match space of A, using the routine permute (we deliberately choose not to overload permutedims from Julia Base, for reasons that become clear below):","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"space(permute(B′,(3,2,1))) == space(A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We can contract two tensors using Einstein summation convention, which takes the interface from TensorOperations.jl. TensorKit.jl reexports the @tensor macro","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]\n@tensor d = A[a,b,c]*A[a,b,c]\nd ≈ scalarAA ≈ normA²","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We hope that the index convention is clear. The := is to create a new tensor D, without the : the result would be written in an existing tensor D, which in this case would yield an error as no tensor D exists. If the contraction yields a scalar, regular assignment with = can be used.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we can factorize a tensor, creating a bipartition of a subset of its indices and its complement. With a plain Julia Array, one would apply permutedims and reshape to cast the array into a matrix before applying e.g. the singular value decomposition. With TensorKit.jl, one just specifies which indices go to the left (rows) and right (columns)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"U, S, Vd = tsvd(A, (1,3), (2,));\n@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA ≈ A′\nU","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that the tsvd routine returns the decomposition of the linear map as three factors, U, S and Vd, each of them a TensorMap, such that Vd is already what is commonly called V'. Furthermore, observe that U is printed differently then A, i.e. as a TensorMap((ℝ^3 ⊗ ℝ^4) ← ProductSpace(ℝ^2)). What this means is that tensors (or more appropriately, TensorMap instances) in TensorKit.jl are always considered to be linear maps between two ProductSpace instances, with","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"codomain(U)\ndomain(U)\ncodomain(A)\ndomain(A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"An instance of TensorMap thus represents a linear map from its domain to its codomain, making it an element of the space of homomorphisms between these two spaces. That space is represented using its own type HomSpace in TensorKit.jl, and which admits a direct constructor as well as a unicode alternative using the symbol → (obtained as \\to+TAB or \\rightarrow+TAB) or ← (obtained as \\leftarrow+TAB).","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"P = space(U)\nspace(U) == HomSpace(ℝ^3 ⊗ ℝ^4, ℝ^2) == (ℝ^3 ⊗ ℝ^4 ← ℝ^2) == ℝ^2 → ℝ^3 ⊗ ℝ^4\n(codomain(P), domain(P))","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Furthermore, a Tensor instance such as A is just a specific case of TensorMap with an empty domain, i.e. a ProductSpace{CartesianSpace,0} instance. Analogously, we can represent a vector v and matrix m as","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"v = randn(ℝ^3)\nM₁ = randn(ℝ^4, ℝ^3)\nM₂ = randn(ℝ^4 → ℝ^2) # alternative syntax for randn(ℝ^2, ℝ^4)\nw = M₁ * v # matrix vector product\nM₃ = M₂ * M₁ # matrix matrix product\nspace(M₃)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that for the construction of M₁, in accordance with how one specifies the dimensions of a matrix (e.g. randn(4,3)), the first space is the codomain and the second the domain. This is somewhat opposite to the general notation for a function f:domain→codomain, so that we also support this more mathemical notation, as illustrated in the construction of M₂. However, as this is confusing from the perspective of rows and columns, we also support the syntax codomain ← domain and actually use this as the default way of printing HomSpace instances.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"The 'matrix vector' or 'matrix matrix' product can be computed between any two TensorMap instances for which the domain of the first matches with the codomain of the second, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"v′ = v ⊗ v\nM₁′ = M₁ ⊗ M₁\nw′ = M₁′ * v′\nw′ ≈ w ⊗ w","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Another example involves checking that U from the singular value decomposition is a unitary, or at least a (left) isometric tensor","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"codomain(U)\ndomain(U)\nspace(U)\nU'*U # should be the identity on the corresponding domain = codomain\nU'*U ≈ one(U'*U)\nP = U*U' # should be a projector\nP*P ≈ P","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, the adjoint of a TensorMap results in a new tensor map (actually a simple wrapper of type AdjointTensorMap <: AbstractTensorMap) with domain and codomain interchanged.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Our original tensor A living in ℝ^4 * ℝ^2 * ℝ^3 is isomorphic to e.g. a linear map ℝ^3 → ℝ^4 * ℝ^2. This is where the full power of permute emerges. It allows to specify a permutation where some indices go to the codomain, and others go to the domain, as in","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A2 = permute(A,(1,2),(3,))\ncodomain(A2)\ndomain(A2)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In fact, tsvd(A, (1,3),(2,)) is a shorthand for tsvd(permute(A,(1,3),(2,))), where tsvd(A::TensorMap) will just compute the singular value decomposition according to the given codomain and domain of A.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note, finally, that the @tensor macro treats all indices at the same footing and thus does not distinguish between codomain and domain. The linear numbering is first all indices in the codomain, followed by all indices in the domain. However, when @tensor creates a new tensor (i.e. when using :=), the default syntax always creates a Tensor, i.e. with all indices in the codomain.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\ncodomain(A′)\ndomain(A′)\n@tensor A2′[(a,b);(c,)] := U[a,c,d]*S[d,e]*Vd[e,b];\ncodomain(A2′)\ndomain(A2′)\n@tensor A2′′[a b; c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA2 ≈ A2′ == A2′′","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"As illustrated for A2′ and A2′′, additional syntax is available that enables one to immediately specify the desired codomain and domain indices.","category":"page"},{"location":"man/tutorial/#Complex-tensors","page":"Tutorial","title":"Complex tensors","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"For applications in e.g. quantum physics, we of course want to work with complex tensors. Trying to create a complex-valued tensor with CartesianSpace indices is of course somewhat contrived and prints a (one-time) warning","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ComplexF64, ℝ^3 ⊗ ℝ^2 ⊗ ℝ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"although most of the above operations will work in the expected way (at your own risk). Indeed, we instead want to work with complex vector spaces","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(ComplexF64, ℂ^3 ⊗ ℂ^2 ⊗ ℂ^4)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"where ℂ is obtained as \\bbC+TAB and we also have the non-Unicode alternative ℂ^n == ComplexSpace(n). Most functionality works exactly the same","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(ℂ^3 * ℂ^2 * ℂ^4);\nC = im*A + (2.5-0.8im)*B\nscalarBA = dot(B,A)\nscalarAA = dot(A,A)\nnormA² = norm(A)^2\nU,S,Vd = tsvd(A,(1,3),(2,));\n@tensor A′[a,b,c] := U[a,c,d]*S[d,e]*Vd[e,b];\nA′ ≈ A\npermute(A,(1,3),(2,)) ≈ U*S*Vd","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"However, trying the following","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"@tensor D[a,b,c,d] := A[a,b,e]*B[d,c,e]\n@tensor d = A[a,b,c]*A[a,b,c]","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"we obtain SpaceMismatch errors. The reason for this is that, with ComplexSpace, an index in a space ℂ^n can only be contracted with an index in the dual space dual(ℂ^n) == (ℂ^n)'. Because of the complex Euclidean inner product, the dual space is equivalent to the complex conjugate space, but not the the space itself.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"dual(ℂ^3) == conj(ℂ^3) == (ℂ^3)'\n(ℂ^3)' == ℂ^3\n@tensor d = conj(A[a,b,c])*A[a,b,c]\nd ≈ normA²","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"This might seem overly strict or puristic, but we believe that it can help to catch errors, e.g. unintended contractions. In particular, contracting two indices both living in ℂ^n would represent an operation that is not invariant under arbitrary unitary basis changes.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"It also makes clear the isomorphism between linear maps ℂ^n → ℂ^m and tensors in ℂ^m ⊗ (ℂ^n)':","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"m = randn(ComplexF64, ℂ^3, ℂ^4)\nm2 = permute(m, (1,2), ())\ncodomain(m2)\nspace(m, 1)\nspace(m, 2)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Hence, spaces become their corresponding dual space if they are 'permuted' from the domain to the codomain or vice versa. Also, spaces in the domain are reported as their dual when probing them with space(A, i). Generalizing matrix vector and matrix matrix multiplication to arbitrary tensor contractions require that the two indices to be contracted have spaces which are each others dual. Knowing this, all the other functionality of tensors with CartesianSpace indices remains the same for tensors with ComplexSpace indices.","category":"page"},{"location":"man/tutorial/#Symmetries","page":"Tutorial","title":"Symmetries","text":"","category":"section"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"So far, the functionality that we have illustrated seems to be just a convenient (or inconvenient?) wrapper around dense multidimensional arrays, e.g. Julia's Base Array. More power becomes visible when involving symmetries. With symmetries, we imply that there is some symmetry action defined on every vector space associated with each of the indices of a TensorMap, and the TensorMap is then required to be equivariant, i.e. it acts as an intertwiner between the tensor product representation on the domain and that on the codomain. By Schur's lemma, this means that the tensor is block diagonal in some basis corresponding to the irreducible representations that can be coupled to by combining the different representations on the different spaces in the domain or codomain. For Abelian symmetries, this does not require a basis change and it just imposes that the tensor has some block sparsity. Let's clarify all of this with some examples.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We start with a simple ℤ₂ symmetry:","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V1 = ℤ₂Space(0=>3,1=>2)\ndim(V1)\nV2 = ℤ₂Space(0=>1,1=>1)\ndim(V2)\nA = randn(V1*V1*V2')\nconvert(Array, A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, we create a 5-dimensional space V1, which has a three-dimensional subspace associated with charge 0 (the trivial irrep of ℤ₂) and a two-dimensional subspace with charge 1 (the non-trivial irrep). Similar for V2, where both subspaces are one- dimensional. Representing the tensor as a dense Array, we see that it is zero in those regions where the charges don't add to zero (modulo 2). Of course, the Tensor(Map) type in TensorKit.jl won't store these zero blocks, and only stores the non-zero information, which we can recognize also in the full Array representation.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"From there on, the resulting tensors support all of the same operations as the ones we encountered in the previous examples.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"B = randn(V1'*V1*V2);\n@tensor C[a,b] := A[a,c,d]*B[c,b,d]\nU,S,V = tsvd(A,(1,3),(2,));\nU'*U # should be the identity on the corresponding domain = codomain\nU'*U ≈ one(U'*U)\nP = U*U' # should be a projector\nP*P ≈ P","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"We also support other abelian symmetries, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = U₁Space(0=>2,1=>1,-1=>1)\ndim(V)\nA = randn(V*V, V)\ndim(A)\nconvert(Array, A)\n\nV = Rep[U₁×ℤ₂]((0, 0) => 2, (1, 1) => 1, (-1, 0) => 1)\ndim(V)\nA = randn(V*V, V)\ndim(A)\nconvert(Array, A)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Here, the dim of a TensorMap returns the number of linearly independent components, i.e. the number of non-zero entries in the case of an abelian symmetry. Also note that we can use × (obtained as \\times+TAB) to combine different symmetry groups. The general space associated with symmetries is a GradedSpace, which is parametrized to the type of symmetry. For a group G, the fully specified type can be obtained as Rep[G], while for more general sectortypes I it can be constructed as Vect[I]. Furthermore, ℤ₂Space (also Z2Space as non-Unicode alternative) and U₁Space (or U1Space) are just convenient synonyms, e.g.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Rep[U₁](0=>3,1=>2,-1=>1) == U1Space(-1=>1,1=>2,0=>3)\nV = U₁Space(1=>2,0=>3,-1=>1)\nfor s in sectors(V)\n @show s, dim(V, s)\nend\nU₁Space(-1=>1,0=>3,1=>2) == GradedSpace(Irrep[U₁](1)=>2, Irrep[U₁](0)=>3, Irrep[U₁](-1)=>1)\nsupertype(GradedSpace)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that GradedSpace is not immediately parameterized by some group G, but actually by the set of irreducible representations of G, denoted as Irrep[G]. Indeed, GradedSpace also supports a grading that is derived from the fusion ring of a (unitary) pre-fusion category. Note furthermore that the order in which the charges and their corresponding subspace dimensionality are specified is irrelevant, and that the charges, henceforth called sectors (which is a more general name for charges or quantum numbers) are of a specific type, in this case Irrep[U₁] == U1Irrep. However, the Vect[I] constructor automatically converts the keys in the list of Pairs it receives to the correct type. Alternatively, we can directly create the sectors of the correct type and use the generic GradedSpace constructor. We can probe the subspace dimension of a certain sector s in a space V with dim(V, s). Finally, note that GradedSpace is also a subtype of EuclideanSpace, which implies that it still has the standard Euclidean inner product and we assume all representations to be unitary.","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"TensorKit.jl also allows for non-abelian symmetries such as SU₂. In this case, the vector space is characterized via the spin quantum number (i.e. the irrep label of SU₂) for each of its subspaces, and is created using SU₂Space (or SU2Space or Rep[SU₂] or Vect[Irrep[SU₂]])","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"V = SU₂Space(0=>2,1/2=>1,1=>1)\ndim(V)\nV == Vect[Irrep[SU₂]](0=>2, 1=>1, 1//2=>1)","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that now V has a two-dimensional subspace with spin zero, and two one-dimensional subspaces with spin 1/2 and spin 1. However, a subspace with spin j has an additional 2j+1 dimensional degeneracy on which the irreducible representation acts. This brings the total dimension to 2*1 + 1*2 + 1*3. Creating a tensor with SU₂ symmetry yields","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"A = randn(V*V, V)\ndim(A)\nconvert(Array, A)\nnorm(A) ≈ norm(convert(Array, A))","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In this case, the full Array representation of the tensor has again many zeros, but it is less obvious to recognize the dense blocks, as there are additional zeros and the numbers in the original tensor data do not match with those in the Array. The reason is of course that the original tensor data now needs to be transformed with a construction known as fusion trees, which are made up out of the Clebsch-Gordan coefficients of the group. Indeed, note that the non-zero blocks are also no longer labeled by a list of sectors, but by pair of fusion trees. This will be explained further in the manual. However, the Clebsch-Gordan coefficients of the group are only needed to actually convert a tensor to an Array. For working with tensors with SU₂Space indices, e.g. contracting or factorizing them, the Clebsch-Gordan coefficients are never needed explicitly. Instead, recoupling relations are used to symbolically manipulate the basis of fusion trees, and this only requires what is known as the topological data of the group (or its representation theory).","category":"page"},{"location":"man/tutorial/","page":"Tutorial","title":"Tutorial","text":"In fact, this formalism extends beyond the case of group representations on vector spaces, and can also deal with super vector spaces (to describe fermions) and more general (unitary) fusion categories. Support for all of these generalizations is present in TensorKit.jl. Indeed, all of these concepts will be explained throughout the remainder of this manual, including several details regarding their implementation. However, to just use tensors and their manipulations (contractions, factorizations, ...) in higher level algorithms (e.g. tensoer network algorithms), one does not need to know or understand most of these details, and one can immediately refer to the general interface of the TensorMap type, discussed on the last page. Adhering to this interface should yield code and algorithms that are oblivious to the underlying symmetries and can thus work with arbitrary symmetric tensors.","category":"page"},{"location":"lib/spaces/#Vector-spaces","page":"Vector spaces","title":"Vector spaces","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/spaces/#Type-hierarchy","page":"Vector spaces","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following types are defined to characterise vector spaces and their properties:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Field\nVectorSpace\nElementarySpace\nGeneralSpace\nCartesianSpace\nComplexSpace\nGradedSpace\nCompositeSpace\nProductSpace\nHomSpace","category":"page"},{"location":"lib/spaces/#TensorKit.Field","page":"Vector spaces","title":"TensorKit.Field","text":"abstract type Field end\n\nAbstract type at the top of the type hierarchy for denoting fields over which vector spaces (or more generally, linear categories) can be defined. Two common fields are ℝ and ℂ, representing the field of real or complex numbers respectively.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.VectorSpace","page":"Vector spaces","title":"TensorKit.VectorSpace","text":"abstract type VectorSpace end\n\nAbstract type at the top of the type hierarchy for denoting vector spaces, or, more accurately, 𝕜-linear categories. All instances of subtypes of VectorSpace will represent objects in 𝕜-linear monoidal categories.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ElementarySpace","page":"Vector spaces","title":"TensorKit.ElementarySpace","text":"abstract type ElementarySpace <: VectorSpace end\n\nElementary finite-dimensional vector space over a field that can be used as the index space corresponding to the indices of a tensor. ElementarySpace is a supertype for all vector spaces (objects) that can be associated with the individual indices of a tensor, as hinted to by its alias IndexSpace.\n\nEvery elementary vector space should respond to the methods conj and dual, returning the complex conjugate space and the dual space respectively. The complex conjugate of the dual space is obtained as dual(conj(V)) === conj(dual(V)). These different spaces should be of the same type, so that a tensor can be defined as an element of a homogeneous tensor product of these spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.GeneralSpace","page":"Vector spaces","title":"TensorKit.GeneralSpace","text":"struct GeneralSpace{𝔽} <: ElementarySpace\n\nA finite-dimensional space over an arbitrary field 𝔽 without additional structure. It is thus characterized by its dimension, and whether or not it is the dual and/or conjugate space. For a real field 𝔽, the space and its conjugate are the same.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.CartesianSpace","page":"Vector spaces","title":"TensorKit.CartesianSpace","text":"struct CartesianSpace <: ElementarySpace\n\nA real Euclidean space ℝ^d, which is therefore self-dual. CartesianSpace has no additonal structure and is completely characterised by its dimension d. This is the vector space that is implicitly assumed in most of matrix algebra.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ComplexSpace","page":"Vector spaces","title":"TensorKit.ComplexSpace","text":"struct ComplexSpace <: ElementarySpace\n\nA standard complex vector space ℂ^d with Euclidean inner product and no additional structure. It is completely characterised by its dimension and whether its the normal space or its dual (which is canonically isomorphic to the conjugate space).\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.GradedSpace","page":"Vector spaces","title":"TensorKit.GradedSpace","text":"struct GradedSpace{I<:Sector, D} <: ElementarySpace\n dims::D\n dual::Bool\nend\n\nA complex Euclidean space with a direct sum structure corresponding to labels in a set I, the objects of which have the structure of a monoid with respect to a monoidal product ⊗. In practice, we restrict the label set to be a set of superselection sectors of type I<:Sector, e.g. the set of distinct irreps of a finite or compact group, or the isomorphism classes of simple objects of a unitary and pivotal (pre-)fusion category.\n\nHere dims represents the degeneracy or multiplicity of every sector.\n\nThe data structure D of dims will depend on the result Base.IteratorElsize(values(I)); if the result is of type HasLength or HasShape, dims will be stored in a NTuple{N,Int} with N = length(values(I)). This requires that a sector s::I can be transformed into an index via s == getindex(values(I), i) and i == findindex(values(I), s). If Base.IteratorElsize(values(I)) results IsInfinite() or SizeUnknown(), a SectorDict{I,Int} is used to store the non-zero degeneracy dimensions with the corresponding sector as key. The parameter D is hidden from the user and should typically be of no concern.\n\nThe concrete type GradedSpace{I,D} with correct D can be obtained as Vect[I], or if I == Irrep[G] for some G<:Group, as Rep[G].\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.CompositeSpace","page":"Vector spaces","title":"TensorKit.CompositeSpace","text":"abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end\n\nAbstract type for composite spaces that are defined in terms of a number of elementary vector spaces of a homogeneous type S<:ElementarySpace.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.ProductSpace","page":"Vector spaces","title":"TensorKit.ProductSpace","text":"struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S}\n\nA ProductSpace is a tensor product space of N vector spaces of type S<:ElementarySpace. Only tensor products between ElementarySpace objects of the same type are allowed.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#TensorKit.HomSpace","page":"Vector spaces","title":"TensorKit.HomSpace","text":"struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}\n codomain::P1\n domain::P2\nend\n\nRepresents the linear space of morphisms with codomain of type P1 and domain of type P2. Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"together with the following specific types for encoding the inner product structure of a space:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"InnerProductStyle","category":"page"},{"location":"lib/spaces/#TensorKit.InnerProductStyle","page":"Vector spaces","title":"TensorKit.InnerProductStyle","text":"InnerProductStyle(V::VectorSpace) -> ::InnerProductStyle\nInnerProductStyle(S::Type{<:VectorSpace}) -> ::InnerProductStyle\n\nReturn the type of inner product for vector spaces, which can be either\n\nNoInnerProduct(): no mapping from dual(V) to conj(V), i.e. no metric\nsubtype of HasInnerProduct: a metric exists, but no further support is implemented.\nEuclideanInnerProduct(): the metric is the identity, such that dual and conjugate spaces are isomorphic.\n\n\n\n\n\n","category":"type"},{"location":"lib/spaces/#Useful-constants","page":"Vector spaces","title":"Useful constants","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following constants are defined to easily create the concrete type of GradedSpace associated with a given type of sector.","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Vect\nRep","category":"page"},{"location":"lib/spaces/#TensorKit.Vect","page":"Vector spaces","title":"TensorKit.Vect","text":"const Vect\n\nA constant of a singleton type used as Vect[I] with I<:Sector a type of sector, to construct or obtain the concrete type GradedSpace{I,D} instances without having to specify D.\n\n\n\n\n\n","category":"constant"},{"location":"lib/spaces/#TensorKit.Rep","page":"Vector spaces","title":"TensorKit.Rep","text":"const Rep\n\nA constant of a singleton type used as Rep[G] with G<:Group a type of group, to construct or obtain the concrete type GradedSpace{Irrep[G],D} instances without having to specify D. Note that Rep[G] == Vect[Irrep[G]].\n\nSee also Irrep and Vect.\n\n\n\n\n\n","category":"constant"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"In this respect, there are also a number of type aliases for the GradedSpace types associated with the most common sectors, namely","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"const ZNSpace{N} = Vect[ZNIrrep{N}]\nconst Z2Space = ZNSpace{2}\nconst Z3Space = ZNSpace{3}\nconst Z4Space = ZNSpace{4}\nconst U1Space = Rep[U₁]\nconst CU1Space = Rep[CU₁]\nconst SU2Space = Rep[SU₂]\n\n# Unicode alternatives\nconst ℤ₂Space = Z2Space\nconst ℤ₃Space = Z3Space\nconst ℤ₄Space = Z4Space\nconst U₁Space = U1Space\nconst CU₁Space = CU1Space\nconst SU₂Space = SU2Space","category":"page"},{"location":"lib/spaces/#s_spacemethods","page":"Vector spaces","title":"Methods","text":"","category":"section"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"Methods often apply similar to e.g. spaces and corresponding tensors or tensor maps, e.g.:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"field\nsectortype\nsectors\nhassector\ndim(::VectorSpace)\ndim(::ElementarySpace, ::Sector)\nreduceddim\ndim(P::ProductSpace{<:ElementarySpace,N}, sector::NTuple{N,<:Sector}) where {N}\ndim(::HomSpace)\ndims\nblocksectors(P::ProductSpace{S,N}) where {S,N}\nblocksectors(::HomSpace)\nhasblock\nblockdim\nfusiontrees(P::ProductSpace{S,N}, blocksector::I) where {S,N,I}\nspace","category":"page"},{"location":"lib/spaces/#TensorKit.field","page":"Vector spaces","title":"TensorKit.field","text":"field(V::VectorSpace) -> Field\n\nReturn the field type over which a vector space is defined.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.sectortype","page":"Vector spaces","title":"TensorKit.sectortype","text":"sectortype(a) -> Type{<:Sector}\n\nReturn the type of sector over which object a (e.g. a representation space or a tensor) is defined. Also works in type domain.\n\n\n\n\n\nsectortype(::AbstractTensorMap) -> Type{I<:Sector}\nsectortype(::Type{<:AbstractTensorMap}) -> Type{I<:Sector}\n\nReturn the type of sector I of a tensor.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.sectors","page":"Vector spaces","title":"TensorKit.sectors","text":"sectors(V::ElementarySpace)\n\nReturn an iterator over the different sectors of V.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.hassector","page":"Vector spaces","title":"TensorKit.hassector","text":"hassector(V::VectorSpace, a::Sector) -> Bool\n\nReturn whether a vector space V has a subspace corresponding to sector a with non-zero dimension, i.e. dim(V, a) > 0.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(V::VectorSpace) -> Int\n\nReturn the total dimension of the vector space V as an Int.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{ElementarySpace, Sector}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(V::ElementarySpace, s::Sector) -> Int\n\nReturn the degeneracy dimension corresponding to the sector s of the vector space V.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.reduceddim","page":"Vector spaces","title":"TensorKit.reduceddim","text":"reduceddim(V::ElementarySpace) -> Int\n\nReturn the sum of all degeneracy dimensions of the vector space V.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dim-Union{Tuple{N}, Tuple{ProductSpace{<:ElementarySpace, N}, NTuple{N, var\"#s2\"} where var\"#s2\"<:Sector}} where N","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}\n-> Int\n\nReturn the total degeneracy dimension corresponding to a tuple of sectors for each of the spaces in the tensor product, obtained as prod(dims(P, s))`.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.dim-Tuple{HomSpace}","page":"Vector spaces","title":"TensorKitSectors.dim","text":"dim(W::HomSpace)\n\nReturn the total dimension of a HomSpace, i.e. the number of linearly independent morphisms that can be constructed within this space.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.dims","page":"Vector spaces","title":"TensorKit.dims","text":"dims(::ProductSpace{S, N}) -> Dims{N} = NTuple{N, Int}\n\nReturn the dimensions of the spaces in the tensor product space as a tuple of integers.\n\n\n\n\n\ndims(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace}\n-> Dims{N} = NTuple{N, Int}\n\nReturn the degeneracy dimensions corresponding to a tuple of sectors s for each of the spaces in the tensor product P.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.blocksectors-Union{Tuple{ProductSpace{S, N}}, Tuple{N}, Tuple{S}} where {S, N}","page":"Vector spaces","title":"TensorKit.blocksectors","text":"blocksectors(P::ProductSpace)\n\nReturn an iterator over the different unique coupled sector labels, i.e. the different fusion outputs that can be obtained by fusing the sectors present in the different spaces that make up the ProductSpace instance.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.blocksectors-Tuple{HomSpace}","page":"Vector spaces","title":"TensorKit.blocksectors","text":"blocksectors(W::HomSpace)\n\nReturn an iterator over the different unique coupled sector labels, i.e. the intersection of the different fusion outputs that can be obtained by fusing the sectors present in the domain, as well as from the codomain.\n\nSee also hasblock.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.hasblock","page":"Vector spaces","title":"TensorKit.hasblock","text":"hasblock(P::ProductSpace, c::Sector)\n\nQuery whether a coupled sector c appears with nonzero dimension in P, i.e. whether blockdim(P, c) > 0.\n\nSee also blockdim and blocksectors.\n\n\n\n\n\nhasblock(W::HomSpace, c::Sector)\n\nQuery whether a coupled sector c appears in both the codomain and domain of W.\n\nSee also blocksectors.\n\n\n\n\n\nhasblock(t::AbstractTensorMap, c::Sector) -> Bool\n\nVerify whether a tensor has a block corresponding to a coupled sector c.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.blockdim","page":"Vector spaces","title":"TensorKit.blockdim","text":"blockdim(P::ProductSpace, c::Sector)\n\nReturn the total dimension of a coupled sector c in the product space, by summing over all dim(P, s) for all tuples of sectors s::NTuple{N, <:Sector} that can fuse to c, counted with the correct multiplicity (i.e. number of ways in which s can fuse to c).\n\nSee also hasblock and blocksectors.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.fusiontrees-Union{Tuple{I}, Tuple{N}, Tuple{S}, Tuple{ProductSpace{S, N}, I}} where {S, N, I}","page":"Vector spaces","title":"TensorKit.fusiontrees","text":"fusiontrees(P::ProductSpace, blocksector::Sector)\n\nReturn an iterator over all fusion trees that can be formed by fusing the sectors present in the different spaces that make up the ProductSpace instance into the coupled sector blocksector.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.space","page":"Vector spaces","title":"TensorKit.space","text":"space(a) -> VectorSpace\n\nReturn the vector space associated to object a.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"The following methods act specifically on ElementarySpace spaces:","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"isdual\ndual\nconj\nflip\n⊕\nzero(::ElementarySpace)\noneunit\nsupremum\ninfimum","category":"page"},{"location":"lib/spaces/#TensorKit.isdual","page":"Vector spaces","title":"TensorKit.isdual","text":"isdual(V::ElementarySpace) -> Bool\n\nReturn wether an ElementarySpace V is normal or rather a dual space. Always returns false for spaces where V == dual(V).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.dual","page":"Vector spaces","title":"TensorKitSectors.dual","text":"dual(V::VectorSpace) -> VectorSpace\n\nReturn the dual space of V; also obtained via V'. This should satisfy dual(dual(V)) == V. It is assumed that typeof(V) == typeof(V').\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#Base.conj","page":"Vector spaces","title":"Base.conj","text":"conj(V::S) where {S<:ElementarySpace} -> S\n\nReturn the conjugate space of V. This should satisfy conj(conj(V)) == V.\n\nFor field(V)==ℝ, conj(V) == V. It is assumed that typeof(V) == typeof(conj(V)).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.flip","page":"Vector spaces","title":"TensorKit.flip","text":"flip(V::S) where {S<:ElementarySpace} -> S\n\nReturn a single vector space of type S that has the same value of isdual as dual(V), but yet is isomorphic to V rather than to dual(V). The spaces flip(V) and dual(V) only differ in the case of GradedSpace{I}.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.:⊕","page":"Vector spaces","title":"TensorKit.:⊕","text":"⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\noplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the direct sum sum of the spaces V₁, V₂, ... Note that all the individual spaces should have the same value for isdual, as otherwise the direct sum is not defined.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#Base.zero-Tuple{ElementarySpace}","page":"Vector spaces","title":"Base.zero","text":"zero(V::S) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. \n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#Base.oneunit","page":"Vector spaces","title":"Base.oneunit","text":"oneunit(V::S) where {S<:ElementarySpace} -> S\n\nReturn the corresponding vector space of type S that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from one(V::S), which returns the empty product space ProductSpace{S,0}(()).\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.supremum","page":"Vector spaces","title":"TensorKit.supremum","text":"supremum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)\n\nReturn the supremum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≿ V₁, V ≿ V₂, ... and no other W ≺ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.infimum","page":"Vector spaces","title":"TensorKit.infimum","text":"infimum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...)\n\nReturn the infimum of a number of elementary spaces, i.e. an instance V::ElementarySpace such that V ≾ V₁, V ≾ V₂, ... and no other W ≻ V has this property. This requires that all arguments have the same value of isdual( ), and also the return value V will have the same value.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"while the following also work on both ElementarySpace and ProductSpace","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"one(::VectorSpace)\nfuse\n⊗(::VectorSpace, ::VectorSpace)\n⊠(::VectorSpace, ::VectorSpace)\nismonomorphic\nisepimorphic\nisisomorphic","category":"page"},{"location":"lib/spaces/#Base.one-Tuple{VectorSpace}","page":"Vector spaces","title":"Base.one","text":"one(::S) where {S<:ElementarySpace} -> ProductSpace{S, 0}\none(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S, 0}\n\nReturn a tensor product of zero spaces of type S, i.e. this is the unit object under the tensor product operation, such that V ⊗ one(V) == V.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.fuse","page":"Vector spaces","title":"TensorKit.fuse","text":"fuse(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\nfuse(P::ProductSpace{S}) where {S<:ElementarySpace} -> S\n\nReturn a single vector space of type S that is isomorphic to the fusion product of the individual spaces V₁, V₂, ..., or the spaces contained in P.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKitSectors.:⊗-Tuple{VectorSpace, VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.:⊗","text":"⊗(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S\n\nCreate a ProductSpace{S}(V₁, V₂, V₃...) representing the tensor product of several elementary vector spaces. For convience, Julia's regular multiplication operator * applied to vector spaces has the same effect.\n\nThe tensor product structure is preserved, see fuse for returning a single elementary space of type S that is isomorphic to this tensor product.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKitSectors.:⊠-Tuple{VectorSpace, VectorSpace}","page":"Vector spaces","title":"TensorKitSectors.:⊠","text":"⊠(V₁::VectorSpace, V₂::VectorSpace)\n\nGiven two vector spaces V₁ and V₂ (ElementarySpace or ProductSpace), or thus, objects of corresponding fusion categories C₁ and C₂, V₁ V₂ constructs the Deligne tensor product, an object in C₁ C₂ which is the natural tensor product of those categories. In particular, the corresponding type of sectors (simple objects) is given by sectortype(V₁ ⊠ V₂) == sectortype(V₁) ⊠ sectortype(V₂) and can be thought of as a tuple of the individual sectors.\n\nThe Deligne tensor product also works in the type domain and for sectors and tensors. For group representations, we have Rep[G₁] ⊠ Rep[G₂] == Rep[G₁ × G₂], i.e. these are the natural representation spaces of the direct product of two groups.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.ismonomorphic","page":"Vector spaces","title":"TensorKit.ismonomorphic","text":"ismonomorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≾ V₂\n\nReturn whether there exist monomorphisms from V₁ to V₂, i.e. 'injective' morphisms with left inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.isepimorphic","page":"Vector spaces","title":"TensorKit.isepimorphic","text":"isepimorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≿ V₂\n\nReturn whether there exist epimorphisms from V₁ to V₂, i.e. 'surjective' morphisms with right inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/#TensorKit.isisomorphic","page":"Vector spaces","title":"TensorKit.isisomorphic","text":"isisomorphic(V₁::VectorSpace, V₂::VectorSpace)\nV₁ ≅ V₂\n\nReturn if V₁ and V₂ are isomorphic, meaning that there exists isomorphisms from V₁ to V₂, i.e. morphisms with left and right inverses.\n\n\n\n\n\n","category":"function"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"There are also specific methods for HomSpace instances, that are used in determining the resuling HomSpace after applying certain tensor operations.","category":"page"},{"location":"lib/spaces/","page":"Vector spaces","title":"Vector spaces","text":"TensorKit.permute(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂}\nTensorKit.select(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂}\nTensorKit.compose(::HomSpace{S}, ::HomSpace{S}) where {S}\ninsertleftunit(::HomSpace, ::Int)\ninsertrightunit(::HomSpace, ::Int)","category":"page"},{"location":"lib/spaces/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {S, N₁, N₂}","page":"Vector spaces","title":"TensorKit.permute","text":"permute(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})\n\nReturn the HomSpace obtained by permuting the indices of the domain and codomain of W according to the permutation p₁ and p₂ respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.select-Union{Tuple{N₂}, Tuple{N₁}, Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, Tuple{NTuple{N₁, Int64}, NTuple{N₂, Int64}}}} where {S, N₁, N₂}","page":"Vector spaces","title":"TensorKit.select","text":"select(W::HomSpace, (p₁, p₂)::Index2Tuple{N₁,N₂})\n\nReturn the HomSpace obtained by a selection from the domain and codomain of W according to the indices in p₁ and p₂ respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.compose-Union{Tuple{S}, Tuple{HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}, HomSpace{S, P1, P2} where {P1<:CompositeSpace{S}, P2<:CompositeSpace{S}}}} where S","page":"Vector spaces","title":"TensorKit.compose","text":"compose(W::HomSpace, V::HomSpace)\n\nObtain the HomSpace that is obtained from composing the morphisms in W and V. For this to be possible, the domain of W must match the codomain of V.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.insertleftunit-Tuple{HomSpace, Int64}","page":"Vector spaces","title":"TensorKit.insertleftunit","text":"insertleftunit(W::HomSpace, i::Int=numind(W) + 1; conj=false, dual=false)\n\nInsert a trivial vector space, isomorphic to the underlying field, at position i. More specifically, adds a left monoidal unit or its dual.\n\nSee also insertrightunit, removeunit.\n\n\n\n\n\n","category":"method"},{"location":"lib/spaces/#TensorKit.insertrightunit-Tuple{HomSpace, Int64}","page":"Vector spaces","title":"TensorKit.insertrightunit","text":"insertrightunit(W::HomSpace, i::Int=numind(W); conj=false, dual=false)\n\nInsert a trivial vector space, isomorphic to the underlying field, after position i. More specifically, adds a right monoidal unit or its dual.\n\nSee also insertleftunit, removeunit.\n\n\n\n\n\n","category":"method"},{"location":"man/intro/#s_intro","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Before providing a typical \"user guide\" and discussing the implementation of TensorKit.jl on the next pages, let us discuss some of the rationale behind this package.","category":"page"},{"location":"man/intro/#ss_whatistensor","page":"Introduction","title":"What is a tensor?","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"At the very start we should ponder about the most suitable and sufficiently general definition of a tensor. A good starting point is the following:","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor t is an element from the tensor product of N vector spaces V_1 V_2 V_N, where N is referred to as the rank or order of the tensor, i.e.\nt V_1 V_2 V_N","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"If you think of a tensor as an object with indices, a rank N tensor has N indices where every index is associated with the corresponding vector space in that it labels a particular basis in that space. We will return to index notation at the very end of this manual.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"As the tensor product of vector spaces is itself a vector space, this implies that a tensor behaves as a vector, i.e. tensors from the same tensor product space can be added and multiplied by scalars. The tensor product is only defined for vector spaces over the same field of scalars, e.g. there is no meaning in ℝ^5 ℂ^3. When all the vector spaces in the tensor product have an inner product, this also implies an inner product for the tensor product space. It is hence clear that the different vector spaces in the tensor product should have some form of homogeneity in their structure, yet they do not need to be all equal and can e.g. have different dimensions. It goes without saying that defining the vector spaces and their properties will be an important part of the definition of a tensor. As a consequence, this also constitutes a significant part of the implementation, and is discussed in the section on Vector spaces.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Aside from the interpretation of a tensor as a vector, we also want to interpret it as a matrix (or more correctly, a linear map) in order to decompose tensors using linear algebra factorisations (e.g. eigenvalue or singular value decomposition). Henceforth, we use the term \"tensor map\" as follows:","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor map t is a linear map from a source or domain W_1 W_2 W_N_2 to a target or codomain V_1 V_2 V_N_1, i.e.\ntW_1 W_2 W_N_2 V_1 V_2 V_N_1","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor of rank N is then just a special case of a tensor map with N_1 = N and N_2 = 0. A contraction between two tensors is just a composition of linear maps (i.e. matrix multiplication), where the contracted indices correspond to the domain of the first tensor and the codomain of the second tensor.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"In order to allow for arbitrary tensor contractions or decompositions, we need to be able to reorganise which vector spaces appear in the domain and the codomain of the tensor map, and in which order. This amounts to defining canonical isomorphisms between the different ways to order and partition the tensor indices (i.e. the vector spaces). For example, a linear map W V is often denoted as a rank 2 tensor in V W^*, where W^* corresponds to the dual space of W. This simple example introduces two new concepts.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Typical vector spaces can appear in the domain and codomain in different related forms, e.g. as normal space or dual space. In fact, the most generic case is that every vector space V has associated with it a dual space V^*, a conjugate space overlineV and a conjugate dual space overlineV^*. The four different vector spaces V, V^*, overlineV and overlineV^* correspond to the representation spaces of respectively the fundamental, dual or contragredient, complex conjugate and dual complex conjugate representation of the general linear group mathsfGL(V). In index notation these spaces are denoted with respectively contravariant (upper), covariant (lower), dotted contravariant and dotted covariant indices.\nFor real vector spaces, the conjugate (dual) space is identical to the normal (dual) space and we only have upper and lower indices, i.e. this is the setting of e.g. general relativity. For (complex) vector spaces with a sesquilinear inner product overlineV V ℂ, the inner product allows to define an isomorphism from the conjugate space to the dual space (known as Riesz representation theorem in the more general context of Hilbert spaces).\nIn particular, in spaces with a Euclidean inner product (the setting of e.g. quantum mechanics), the conjugate and dual space are naturally isomorphic (because the dual and conjugate representation of the unitary group are the same). Again we only need upper and lower indices (or kets and bras).\nFinally, in ℝ^d with a Euclidean inner product, these four different spaces are all equivalent and we only need one type of index. The space is completely characterized by its dimension d. This is the setting of much of classical mechanics and we refer to such tensors as cartesian tensors and the corresponding space as cartesian space. These are the tensors that can equally well be represented as multidimensional arrays (i.e. using some AbstractArray{<:Real,N} in Julia) without loss of structure.\nThe implementation of all of this is discussed in Vector spaces.\nIn the generic case, the identification between maps W V and tensors in V W^* is not an equivalence but an isomorphism, which needs to be defined. Similarly, there is an isomorphism between between V W and W V that can be non-trivial (e.g. in the case of fermions / super vector spaces). The correct formalism here is provided by theory of monoidal categories, which is introduced on the next page. Nonetheless, we try to hide these canonical isomorphisms from the user wherever possible, and one does not need to know category theory to be able to use this package.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"This brings us to our final (yet formal) definition","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"A tensor (map) is a homomorphism between two objects from the category mathbfVect (or some subcategory thereof). In practice, this will be mathbfFinVect, the category of finite dimensional vector spaces. More generally even, our concept of a tensor makes sense, in principle, for any linear (a.k.a. mathbfVect-enriched) monoidal category. We refer to the next page on \"Monoidal categories and their properties\".","category":"page"},{"location":"man/intro/#ss_symmetries","page":"Introduction","title":"Symmetries and block sparsity","text":"","category":"section"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Physical problems often have some symmetry, i.e. the setup is invariant under the action of a group mathsfG which acts on the vector spaces V in the problem according to a certain representation. Having quantum mechanics in mind, TensorKit.jl is so far restricted to unitary representations. A general representation space V can be specified as the number of times every irreducible representation (irrep) a of mathsfG appears, i.e.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"V = bigoplus_a ℂ^n_a R_a","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"with R_a the space associated with irrep a of mathsfG, which itself has dimension d_a (often called the quantum dimension), and n_a the number of times this irrep appears in V. If the unitary irrep a for g mathsfG is given by u_a(g), then there exists a specific basis for V such that the group action of mathsfG on V is given by the unitary representation","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"u(g) = bigoplus_a 𝟙_n_a u_a(g)","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"with 𝟙_n_a the n_a n_a identity matrix. The total dimension of V is given by _a n_a d_a.","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"The reason for implementing symmetries is to exploit the computation and memory gains resulting from restricting to tensor maps tW_1 W_2 W_N_2 V_1 V_2 V_N_1 that are equivariant under the symmetry, i.e. that act as intertwiners between the symmetry action on the domain and the codomain. Indeed, such tensors should be block diagonal because of Schur's lemma, but only after we couple the individual irreps in the spaces W_i to a joint irrep, which is then again split into the individual irreps of the spaces V_i. The basis change from the tensor product of irreps in the (co)domain to the joint irrep is implemented by a sequence of Clebsch–Gordan coefficients, also known as a fusion (or splitting) tree. We implement the necessary machinery to manipulate these fusion trees under index permutations and repartitions for arbitrary groups mathsfG. In particular, this fits with the formalism of monoidal categories, and more specifically fusion categories, and only requires the topological data of the group, i.e. the fusion rules of the irreps, their quantum dimensions and the F-symbol (6j-symbol or more precisely Racah's W-symbol in the case of mathsfSU_2). In particular, we don't actually need the Clebsch–Gordan coefficients themselves (but they can be useful for checking purposes).","category":"page"},{"location":"man/intro/","page":"Introduction","title":"Introduction","text":"Hence, a second major part of TensorKit.jl is the interface and implementation for specifying symmetries, and further details are provided in Sectors, representation spaces and fusion trees.","category":"page"},{"location":"lib/sectors/#Symmetry-sectors-and-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"CurrentModule = TensorKit","category":"page"},{"location":"lib/sectors/#Type-hierarchy","page":"Symmetry sectors and fusion trees","title":"Type hierarchy","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Sector\nSectorValues\nFusionStyle\nBraidingStyle\nAbstractIrrep\nTrivial\nZNIrrep\nU1Irrep\nSU2Irrep\nCU1Irrep\nProductSector\nFermionParity\nFermionNumber\nFermionSpin\nFibonacciAnyon\nIsingAnyon","category":"page"},{"location":"lib/sectors/#TensorKitSectors.Sector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Sector","text":"abstract type Sector end\n\nAbstract type for representing the (isomorphism classes of) simple objects in (unitary and pivotal) (pre-)fusion categories, e.g. the irreducible representations of a finite or compact group. Subtypes I<:Sector as the set of labels of a GradedSpace.\n\nEvery new I<:Sector should implement the following methods:\n\none(::Type{I}): unit element of I\nconj(a::I): a, conjugate or dual label of a\n⊗(a::I, b::I): iterable with unique fusion outputs of a b (i.e. don't repeat in case of multiplicities)\nNsymbol(a::I, b::I, c::I): number of times c appears in a ⊗ b, i.e. the multiplicity\nFusionStyle(::Type{I}): UniqueFusion(), SimpleFusion() or GenericFusion()\nBraidingStyle(::Type{I}): Bosonic(), Fermionic(), Anyonic(), ...\nFsymbol(a::I, b::I, c::I, d::I, e::I, f::I): F-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\nRsymbol(a::I, b::I, c::I): R-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\n\nand optionally\n\ndim(a::I): quantum dimension of sector a\nfrobeniusschur(a::I): Frobenius-Schur indicator of a\nBsymbol(a::I, b::I, c::I): B-symbol: scalar (in case of UniqueFusion/SimpleFusion) or matrix (in case of GenericFusion)\ntwist(a::I) -> twist of sector a\n\nFurthermore, iterate and Base.IteratorSize should be made to work for the singleton type SectorValues{I}.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.SectorValues","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.SectorValues","text":"struct SectorValues{I<:Sector}\n\nSingleton type to represent an iterator over the possible values of type I, whose instance is obtained as values(I). For a new I::Sector, the following should be defined\n\nBase.iterate(::SectorValues{I}[, state]): iterate over the values\nBase.IteratorSize(::Type{SectorValues{I}}): HasLenght(), SizeUnkown() or IsInfinite() depending on whether the number of values of type I is finite (and sufficiently small) or infinite; for a large number of values, SizeUnknown() is recommend because this will trigger the use of GenericGradedSpace.\n\nIf IteratorSize(I) == HasLength(), also the following must be implemented:\n\nBase.length(::SectorValues{I}): the number of different values\nBase.getindex(::SectorValues{I}, i::Int): a mapping between an index i and an instance of I\nfindindex(::SectorValues{I}, c::I): reverse mapping between a value c::I and an index i::Integer ∈ 1:length(values(I))\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FusionStyle","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FusionStyle","text":"FusionStyle(::Sector)\nFusionStyle(I::Type{<:Sector})\n\nTrait to describe the fusion behavior of sectors of type I, which can be either\n\nUniqueFusion(): single fusion output when fusing two sectors;\nSimpleFusion(): multiple outputs, but every output occurs at most one, also known as multiplicity-free (e.g. irreps of SU(2));\nGenericFusion(): multiple outputs that can occur more than once (e.g. irreps of SU(3)).\n\nThere is an abstract supertype MultipleFusion of which both SimpleFusion and GenericFusion are subtypes. Furthermore, there is a type alias MultiplicityFreeFusion for those fusion types which do not require muliplicity labels, i.e. MultiplicityFreeFusion = Union{UniqueFusion,SimpleFusion}.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.BraidingStyle","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.BraidingStyle","text":"BraidingStyle(::Sector) -> ::BraidingStyle\nBraidingStyle(I::Type{<:Sector}) -> ::BraidingStyle\n\nReturn the type of braiding and twist behavior of sectors of type I, which can be either\n\nBosonic(): symmetric braiding with trivial twist (i.e. identity)\nFermionic(): symmetric braiding with non-trivial twist (squares to identity)\nAnyonic(): general R_(ab)^c phase or matrix (depending on SimpleFusion or GenericFusion fusion) and arbitrary twists\n\nNote that Bosonic and Fermionic are subtypes of SymmetricBraiding, which means that braids are in fact equivalent to crossings (i.e. braiding twice is an identity: isone(Rsymbol(b,a,c)*Rsymbol(a,b,c)) == true) and permutations are uniquely defined.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.AbstractIrrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.AbstractIrrep","text":"abstract type AbstractIrrep{G<:Group} <: Sector end\n\nAbstract supertype for sectors which corresponds to irreps (irreducible representations) of a group G. As we assume unitary representations, these would be finite groups or compact Lie groups. Note that this could also include projective rather than linear representations.\n\nActual concrete implementations of those irreps can be obtained as Irrep[G], or via their actual name, which generically takes the form (asciiG)Irrep, i.e. the ASCII spelling of the group name followed by Irrep.\n\nAll irreps have BraidingStyle equal to Bosonic() and thus trivial twists.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.Trivial","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Trivial","text":"Trivial\n\nSingleton type to represent the trivial sector, i.e. the trivial representation of the trivial group. This is equivalent to Rep[ℤ₁], or the unit object of the category Vect of ordinary vector spaces.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.ZNIrrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.ZNIrrep","text":"struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}\nZNIrrep{N}(n::Integer)\nIrrep[ℤ{N}](n::Integer)\n\nRepresents irreps of the group ℤ_N for some value of N<64. (We need 2*(N-1) <= 127 in order for a ⊗ b to work correctly.) For N equals 2, 3 or 4, ℤ{N} can be replaced by ℤ₂, ℤ₃, ℤ₄. An arbitrary Integer n can be provided to the constructor, but only the value mod(n, N) is relevant.\n\nFields\n\nn::Int8: the integer label of the irrep, modulo N.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.U1Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.U1Irrep","text":"struct U1Irrep <: AbstractIrrep{U₁}\nU1Irrep(charge::Real)\nIrrep[U₁](charge::Real)\n\nRepresents irreps of the group U₁. The irrep is labelled by a charge, which should be an integer for a linear representation. However, it is often useful to allow half integers to represent irreps of U₁ subgroups of SU₂, such as the Sz of spin-1/2 system. Hence, the charge is stored as a HalfInt from the package HalfIntegers.jl, but can be entered as arbitrary Real. The sequence of the charges is: 0, 1/2, -1/2, 1, -1, ...\n\nFields\n\ncharge::HalfInt: the label of the irrep, which can be any half integer.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.SU2Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.SU2Irrep","text":"struct SU2Irrep <: AbstractIrrep{SU₂}\nSU2Irrep(j::Real)\nIrrep[SU₂](j::Real)\n\nRepresents irreps of the group SU₂. The irrep is labelled by a half integer j which can be entered as an abitrary Real, but is stored as a HalfInt from the HalfIntegers.jl package.\n\nFields\n\nj::HalfInt: the label of the irrep, which can be any non-negative half integer.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.CU1Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.CU1Irrep","text":"struct CU1Irrep <: AbstractIrrep{CU₁}\nCU1Irrep(j, s = ifelse(j>zero(j), 2, 0))\nIrrep[CU₁](j, s = ifelse(j>zero(j), 2, 0))\n\nRepresents irreps of the group U₁ C (U₁ and charge conjugation or reflection), which is also known as just O₂. \n\nFields\n\nj::HalfInt: the value of the U₁ charge.\ns::Int: the representation of charge conjugation.\n\nThey can take values:\n\nif j == 0, s = 0 (trivial charge conjugation) or s = 1 (non-trivial charge conjugation)\nif j > 0, s = 2 (two-dimensional representation)\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.ProductSector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.ProductSector","text":"ProductSector{T<:SectorTuple}\n\nRepresents the Deligne tensor product of sectors. The type parameter T is a tuple of the component sectors. The recommended way to construct a ProductSector is using the deligneproduct (⊠) operator on the components.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionParity","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionParity","text":"FermionParity <: Sector\n\nRepresents sectors with fermion parity. The fermion parity is a ℤ₂ quantum number that yields an additional sign when two odd fermions are exchanged.\n\nFields\n\nisodd::Bool: indicates whether the fermion parity is odd (true) or even (false).\n\nSee also: FermionNumber, FermionSpin\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionNumber","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionNumber","text":"const FermionNumber = U1Irrep ⊠ FermionParity\nFermionNumber(a::Int)\n\nRepresents the fermion number as the direct product of a U₁ irrep a and a fermion parity, with the restriction that the fermion parity is odd if and only if a is odd.\n\nSee also: U1Irrep, FermionParity\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FermionSpin","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FermionSpin","text":"const FermionSpin = SU2Irrep ⊠ FermionParity\nFermionSpin(j::Real)\n\nRepresents the fermion spin as the direct product of a SU₂ irrep j and a fermion parity, with the restriction that the fermion parity is odd if 2 * j is odd.\n\nSee also: SU2Irrep, FermionParity\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.FibonacciAnyon","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.FibonacciAnyon","text":"struct FibonacciAnyon <: Sector\nFibonacciAnyon(s::Symbol)\n\nRepresents the anyons of the Fibonacci modular fusion category. It can take two values, corresponding to the trivial sector FibonacciAnyon(:I) and the non-trivial sector FibonacciAnyon(:τ) with fusion rules τ τ = 1 τ.\n\nFields\n\nisone::Bool: indicates whether the sector corresponds the to trivial anyon :I (true), or the non-trivial anyon :τ (false).\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKitSectors.IsingAnyon","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.IsingAnyon","text":"struct IsingAnyon <: Sector\nIsingAnyon(s::Symbol)\n\nRepresents the anyons of the Ising modular fusion category. It can take three values, corresponding to the trivial sector IsingAnyon(:I) and the non-trivial sectors IsingAnyon(:σ) and IsingAnyon(:ψ), with fusion rules ψ ψ = 1, σ ψ = σ, and σ σ = 1 ψ.\n\nFields\n\ns::Symbol: the label of the represented anyon, which can be :I, :σ, or :ψ.\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#Useful-constants","page":"Symmetry sectors and fusion trees","title":"Useful constants","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Irrep","category":"page"},{"location":"lib/sectors/#TensorKitSectors.Irrep","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Irrep","text":"const Irrep\n\nA constant of a singleton type used as Irrep[G] with G<:Group a type of group, to construct or obtain a concrete subtype of AbstractIrrep{G} that implements the data structure used to represent irreducible representations of the group G.\n\n\n\n\n\n","category":"constant"},{"location":"lib/sectors/#Methods-for-defining-and-characterizing-Sector-subtypes","page":"Symmetry sectors and fusion trees","title":"Methods for defining and characterizing Sector subtypes","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Base.one(::Sector)\ndual(::Sector)\nNsymbol\n⊗\nFsymbol\nRsymbol\nBsymbol\ndim(::Sector)\nfrobeniusschur\ntwist(::Sector)\nBase.isreal(::Type{<:Sector})\nTensorKitSectors.sectorscalartype\ndeligneproduct(::Sector, ::Sector)","category":"page"},{"location":"lib/sectors/#Base.one-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"Base.one","text":"one(::Sector) -> Sector\none(::Type{<:Sector}) -> Sector\n\nReturn the unit element within this type of sector.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.dual-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.dual","text":"dual(a::Sector) -> Sector\n\nReturn the conjugate label conj(a).\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.Nsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Nsymbol","text":"Nsymbol(a::I, b::I, c::I) where {I<:Sector} -> Integer\n\nReturn an Integer representing the number of times c appears in the fusion product a ⊗ b. Could be a Bool if FusionStyle(I) == UniqueFusion() or SimpleFusion().\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.:⊗","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.:⊗","text":"⊗(a::I, b::I...) where {I<:Sector}\notimes(a::I, b::I...) where {I<:Sector}\n\nReturn an iterable of elements of c::I that appear in the fusion product a ⊗ b.\n\nNote that every element c should appear at most once, fusion degeneracies (if FusionStyle(I) == GenericFusion()) should be accessed via Nsymbol(a, b, c).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Fsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Fsymbol","text":"Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:Sector}\n\nReturn the F-symbol F^abc_d that associates the two different fusion orders of sectors a, b and c into an ouput sector d, using either an intermediate sector a b e or b c f:\n\na-<-μ-<-e-<-ν-<-d a-<-λ-<-d\n ∨ ∨ -> Fsymbol(a,b,c,d,e,f)[μ,ν,κ,λ] ∨\n b c f\n v\n b-<-κ\n ∨\n c\n\nIf FusionStyle(I) is UniqueFusion or SimpleFusion, the F-symbol is a number. Otherwise it is a rank 4 array of size (Nsymbol(a, b, e), Nsymbol(e, c, d), Nsymbol(b, c, f), Nsymbol(a, f, d)).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Rsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Rsymbol","text":"Rsymbol(a::I, b::I, c::I) where {I<:Sector}\n\nReturns the R-symbol R^ab_c that maps between c a b and c b a as in\n\na -<-μ-<- c b -<-ν-<- c\n ∨ -> Rsymbol(a,b,c)[μ,ν] v\n b a\n\nIf FusionStyle(I) is UniqueFusion() or SimpleFusion(), the R-symbol is a number. Otherwise it is a square matrix with row and column size Nsymbol(a,b,c) == Nsymbol(b,a,c).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.Bsymbol","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.Bsymbol","text":"Bsymbol(a::I, b::I, c::I) where {I<:Sector}\n\nReturn the value of B^ab_c which appears in transforming a splitting vertex into a fusion vertex using the transformation\n\na -<-μ-<- c a -<-ν-<- c\n ∨ -> √(dim(c)/dim(a)) * Bsymbol(a,b,c)[μ,ν] ∧\n b dual(b)\n\nIf FusionStyle(I) is UniqueFusion() or SimpleFusion(), the B-symbol is a number. Otherwise it is a square matrix with row and column size Nsymbol(a, b, c) == Nsymbol(c, dual(b), a).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.dim-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.dim","text":"dim(a::Sector)\n\nReturn the (quantum) dimension of the sector a.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.frobeniusschur","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.frobeniusschur","text":"frobeniusschur(a::Sector)\n\nReturn the Frobenius-Schur indicator of a sector a.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.twist-Tuple{Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.twist","text":"twist(a::Sector)\n\nReturn the twist of a sector a\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#Base.isreal-Tuple{Type{<:Sector}}","page":"Symmetry sectors and fusion trees","title":"Base.isreal","text":"isreal(::Type{<:Sector}) -> Bool\n\nReturn whether the topological data (Fsymbol, Rsymbol) of the sector is real or not (in which case it is complex).\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKitSectors.sectorscalartype","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.sectorscalartype","text":"sectorscalartype(I::Type{<:Sector}) -> Type\n\nReturn the scalar type of the topological data (Fsymbol, Rsymbol) of the sector I.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKitSectors.deligneproduct-Tuple{Sector, Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.deligneproduct","text":"⊠(s₁::Sector, s₂::Sector)\ndeligneproduct(s₁::Sector, s₂::Sector)\n\nGiven two sectors s₁ and s₂, which label an isomorphism class of simple objects in a fusion category C₁ and C₂, s1 ⊠ s2 (obtained as \\boxtimes+TAB) labels the isomorphism class of simple objects in the Deligne tensor product category C₁ C₂.\n\nThe Deligne tensor product also works in the type domain and for spaces and tensors. For group representations, we have Irrep[G₁] ⊠ Irrep[G₂] == Irrep[G₁ × G₂].\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Compile all revelant methods for a sector:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"TensorKitSectors.precompile_sector","category":"page"},{"location":"lib/sectors/#TensorKitSectors.precompile_sector","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.precompile_sector","text":"precompile_sector(I::Type{<:Sector})\n\nPrecompile common methods for the given sector type.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Types-and-methods-for-groups","page":"Symmetry sectors and fusion trees","title":"Types and methods for groups","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Types and constants:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"# TODO: add documentation for the following types\nGroup\nTensorKitSectors.AbelianGroup\nU₁\nℤ{N} where N\nSU{N} where N\nconst SU₂ = SU{2}\nProductGroup","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Specific methods:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"×","category":"page"},{"location":"lib/sectors/#TensorKitSectors.:×","page":"Symmetry sectors and fusion trees","title":"TensorKitSectors.:×","text":"×(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}\ntimes(G::Vararg{Type{<:Group}}) -> ProductGroup{Tuple{G...}}\n\nConstruct the direct product of a (list of) groups.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Methods-for-defining-and-generating-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Methods for defining and generating fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"FusionTree\nfusiontrees(uncoupled::NTuple{N,I}, coupled::I,\n isdual::NTuple{N,Bool}) where {N,I<:Sector}","category":"page"},{"location":"lib/sectors/#TensorKit.FusionTree","page":"Symmetry sectors and fusion trees","title":"TensorKit.FusionTree","text":"struct FusionTree{I, N, M, L}\n\nRepresents a fusion tree of sectors of type I<:Sector, fusing (or splitting) N uncoupled sectors to a coupled sector. It actually represents a splitting tree, but fusion tree is a more common term.\n\nFields\n\nuncoupled::NTuple{N,I}: the uncoupled sectors coming out of the splitting tree, before the possible 𝑍 isomorphism (see isdual).\ncoupled::I: the coupled sector.\nisdual::NTuple{N,Bool}: indicates whether a 𝑍 isomorphism is present (true) or not (false) for each uncoupled sector.\ninnerlines::NTuple{M,I}: the labels of the M=max(0, N-2) inner lines of the splitting tree.\nvertices::NTuple{L,Int}: the integer values of the L=max(0, N-1) vertices of the splitting tree. If FusionStyle(I) isa MultiplicityFreeFusion, then vertices is simply equal to the constant value ntuple(n->1, L).\n\n\n\n\n\n","category":"type"},{"location":"lib/sectors/#TensorKit.fusiontrees-Union{Tuple{I}, Tuple{N}, Tuple{NTuple{N, I}, I, NTuple{N, Bool}}} where {N, I<:Sector}","page":"Symmetry sectors and fusion trees","title":"TensorKit.fusiontrees","text":"fusiontrees(uncoupled::NTuple{N,I}[,\n coupled::I=one(I)[, isdual::NTuple{N,Bool}=ntuple(n -> false, length(uncoupled))]])\n where {N,I<:Sector} -> FusionTreeIterator{I,N,I}\n\nReturn an iterator over all fusion trees with a given coupled sector label coupled and uncoupled sector labels and isomorphisms uncoupled and isdual respectively.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#Methods-for-manipulating-fusion-trees","page":"Symmetry sectors and fusion trees","title":"Methods for manipulating fusion trees","text":"","category":"section"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"For manipulating single fusion trees, the following internal methods are defined:","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"insertat\nsplit\nmerge\nelementary_trace\nplanar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}\nartin_braid\nbraid(f::FusionTree{I,N}, levels::NTuple{N,Int}, p::NTuple{N,Int}) where {I<:Sector,N}\npermute(f::FusionTree{I,N}, p::NTuple{N,Int}) where {I<:Sector,N}","category":"page"},{"location":"lib/sectors/#TensorKit.insertat","page":"Symmetry sectors and fusion trees","title":"TensorKit.insertat","text":"insertat(f::FusionTree{I, N₁}, i::Int, f₂::FusionTree{I, N₂})\n-> <:AbstractDict{<:FusionTree{I, N₁+N₂-1}, <:Number}\n\nAttach a fusion tree f₂ to the uncoupled leg i of the fusion tree f₁ and bring it into a linear combination of fusion trees in standard form. This requires that f₂.coupled == f₁.uncoupled[i] and f₁.isdual[i] == false.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.split","page":"Symmetry sectors and fusion trees","title":"TensorKit.split","text":"split(f::FusionTree{I, N}, M::Int)\n-> (::FusionTree{I, M}, ::FusionTree{I, N-M+1})\n\nSplit a fusion tree into two. The first tree has as uncoupled sectors the first M uncoupled sectors of the input tree f, whereas its coupled sector corresponds to the internal sector between uncoupled sectors M and M+1 of the original tree f. The second tree has as first uncoupled sector that same internal sector of f, followed by remaining N-M uncoupled sectors of f. It couples to the same sector as f. This operation is the inverse of insertat in the sense that if f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁).\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.merge","page":"Symmetry sectors and fusion trees","title":"TensorKit.merge","text":"merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = 1)\n-> <:AbstractDict{<:FusionTree{I, N₁+N₂}, <:Number}\n\nMerge two fusion trees together to a linear combination of fusion trees whose uncoupled sectors are those of f₁ followed by those of f₂, and where the two coupled sectors of f₁ and f₂ are further fused to c. In case of FusionStyle(I) == GenericFusion(), also a degeneracy label μ for the fusion of the coupled sectors of f₁ and f₂ to c needs to be specified.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.elementary_trace","page":"Symmetry sectors and fusion trees","title":"TensorKit.elementary_trace","text":"elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} -> <:AbstractDict{FusionTree{I,N-2}, <:Number}\n\nPerform an elementary trace of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.planar_trace-Union{Tuple{N₃}, Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N₃, Int64}, NTuple{N₃, Int64}}} where {I<:Sector, N, N₃}","page":"Symmetry sectors and fusion trees","title":"TensorKit.planar_trace","text":"planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃}\n -> <:AbstractDict{FusionTree{I,N-2*N₃}, <:Number}\n\nPerform a planar trace of the uncoupled indices of the fusion tree f at q1 with those at q2, where q1[i] is connected to q2[i] for all i. The result is returned as a dictionary of output trees and corresponding coefficients.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.artin_braid","page":"Symmetry sectors and fusion trees","title":"TensorKit.artin_braid","text":"artin_braid(f::FusionTree, i; inv::Bool = false) -> <:AbstractDict{typeof(f), <:Number}\n\nPerform an elementary braid (Artin generator) of neighbouring uncoupled indices i and i+1 on a fusion tree f, and returns the result as a dictionary of output trees and corresponding coefficients.\n\nThe keyword inv determines whether index i will braid above or below index i+1, i.e. applying artin_braid(f′, i; inv = true) to all the outputs f′ of artin_braid(f, i; inv = false) and collecting the results should yield a single fusion tree with non-zero coefficient, namely f with coefficient 1. This keyword has no effect if BraidingStyle(sectortype(f)) isa SymmetricBraiding.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#TensorKit.braid-Union{Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N, Int64}, NTuple{N, Int64}}} where {I<:Sector, N}","page":"Symmetry sectors and fusion trees","title":"TensorKit.braid","text":"braid(f::FusionTree{<:Sector, N}, levels::NTuple{N, Int}, p::NTuple{N, Int})\n-> <:AbstractDict{typeof(t), <:Number}\n\nPerform a braiding of the uncoupled indices of the fusion tree f and return the result as a <:AbstractDict of output trees and corresponding coefficients. The braiding is determined by specifying that the new sector at position k corresponds to the sector that was originally at the position i = p[k], and assigning to every index i of the original fusion tree a distinct level or depth levels[i]. This permutation is then decomposed into elementary swaps between neighbouring indices, where the swaps are applied as braids such that if i and j cross, τ_ij is applied if levels[i] < levels[j] and τ_ji^-1 if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.permute-Union{Tuple{N}, Tuple{I}, Tuple{FusionTree{I, N}, NTuple{N, Int64}}} where {I<:Sector, N}","page":"Symmetry sectors and fusion trees","title":"TensorKit.permute","text":"permute(f::FusionTree, p::NTuple{N, Int}) -> <:AbstractDict{typeof(t), <:Number}\n\nPerform a permutation of the uncoupled indices of the fusion tree f and returns the result as a <:AbstractDict of output trees and corresponding coefficients; this requires that BraidingStyle(sectortype(f)) isa SymmetricBraiding.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"These can be composed to implement elementary manipulations of fusion-splitting tree pairs, according to the following methods","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"# TODO: add documentation for the following methods\nTensorKit.bendright\nTensorKit.bendleft\nTensorKit.foldright\nTensorKit.foldleft\nTensorKit.cycleclockwise\nTensorKit.cycleanticlockwise","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"Finally, these are used to define large manipulations of fusion-splitting tree pairs, which are then used in the index manipulation of AbstractTensorMap objects. The following methods defined on fusion splitting tree pairs have an associated definition for tensors.","category":"page"},{"location":"lib/sectors/","page":"Symmetry sectors and fusion trees","title":"Symmetry sectors and fusion trees","text":"repartition\ntranspose(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}\nbraid(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple, ::IndexTuple, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}\npermute(::FusionTree{I}, ::FusionTree{I}, ::IndexTuple{N₁}, ::IndexTuple{N₂}) where {I<:Sector,N₁,N₂}","category":"page"},{"location":"lib/sectors/#TensorKit.repartition","page":"Symmetry sectors and fusion trees","title":"TensorKit.repartition","text":"repartition(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, N::Int) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N}, FusionTree{I, N₁+N₂-N}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (f₁) and incoming sectors (f₂) respectively (with identical coupled sector f₁.coupled == f₂.coupled). Computes new trees and corresponding coefficients obtained from repartitioning the tree by bending incoming to outgoing sectors (or vice versa) in order to have N outgoing sectors.\n\n\n\n\n\nrepartition(tsrc::AbstractTensorMap{S}, N₁::Int, N₂::Int; copy::Bool=false) where {S}\n -> tdst::AbstractTensorMap{S,N₁,N₂}\n\nReturn tensor tdst obtained by repartitioning the indices of t. The codomain and domain of tdst correspond to the first N₁ and last N₂ spaces of t, respectively.\n\nIf copy=false, tdst might share data with tsrc whenever possible. Otherwise, a copy is always made.\n\nTo repartition into an existing destination, see repartition!.\n\n\n\n\n\n","category":"function"},{"location":"lib/sectors/#Base.transpose-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"Base.transpose","text":"transpose(f₁::FusionTree{I}, f₂::FusionTree{I},\n p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.braid-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N, Int64} where N, NTuple{N, Int64} where N, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"TensorKit.braid","text":"braid(f₁::FusionTree{I}, f₂::FusionTree{I},\n levels1::IndexTuple, levels2::IndexTuple,\n p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a fusion-splitting tree pair that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the splitting tree f₁ and fusion tree f₂, such that the incoming sectors f₂.uncoupled are fused to f₁.coupled == f₂.coupled and then to the outgoing sectors f₁.uncoupled. Compute new trees and corresponding coefficients obtained from repartitioning and braiding the tree such that sectors p1 become outgoing and sectors p2 become incoming. The uncoupled indices in splitting tree f₁ and fusion tree f₂ have levels (or depths) levels1 and levels2 respectively, which determines how indices braid. In particular, if i and j cross, τ_ij is applied if levels[i] < levels[j] and τ_ji^-1 if levels[i] > levels[j]. This does not allow to encode the most general braid, but a general braid can be obtained by combining such operations.\n\n\n\n\n\n","category":"method"},{"location":"lib/sectors/#TensorKit.permute-Union{Tuple{N₂}, Tuple{N₁}, Tuple{I}, Tuple{FusionTree{I}, FusionTree{I}, NTuple{N₁, Int64}, NTuple{N₂, Int64}}} where {I<:Sector, N₁, N₂}","page":"Symmetry sectors and fusion trees","title":"TensorKit.permute","text":"permute(f₁::FusionTree{I}, f₂::FusionTree{I},\n p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int}) where {I, N₁, N₂}\n-> <:AbstractDict{Tuple{FusionTree{I, N₁}, FusionTree{I, N₂}}, <:Number}\n\nInput is a double fusion tree that describes the fusion of a set of incoming uncoupled sectors to a set of outgoing uncoupled sectors, represented using the individual trees of outgoing (t1) and incoming sectors (t2) respectively (with identical coupled sector t1.coupled == t2.coupled). Computes new trees and corresponding coefficients obtained from repartitioning and permuting the tree such that sectors p1 become outgoing and sectors p2 become incoming.\n\n\n\n\n\n","category":"method"},{"location":"#TensorKit.jl","page":"Home","title":"TensorKit.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"A Julia package for large-scale tensor computations, with a hint of category theory.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = TensorKit","category":"page"},{"location":"#Package-summary","page":"Home","title":"Package summary","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"TensorKit.jl aims to be a generic package for working with tensors as they appear throughout the physical sciences. TensorKit implements a parametric type Tensor (which is actually a specific case of the type TensorMap) and defines for these types a number of vector space operations (scalar multiplication, addition, norms and inner products), index operations (permutations) and linear algebra operations (multiplication, factorizations). Finally, tensor contractions can be performed using the @tensor macro from TensorOperations.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Currently, most effort is oriented towards tensors as they appear in the context of quantum many body physics and in particular the field of tensor networks. Such tensors often have large dimensions and take on a specific structure when symmetries are present. To deal with generic symmetries, we employ notations and concepts from category theory all the way down to the definition of a tensor.","category":"page"},{"location":"","page":"Home","title":"Home","text":"At the same time, TensorKit.jl focusses on computational efficiency and performance. The underlying storage of a tensor's data can be any DenseArray. Currently, certain operations are already multithreaded, either by distributing the different blocks in case of a structured tensor (i.e. with symmetries) or by using multithreading provided by the package Strided.jl. In the future, we also plan to investigate using CuArrays as underlying storage for the tensors data, so as to leverage GPUs for the different operations defined on tensors.","category":"page"},{"location":"#Contents-of-the-manual","page":"Home","title":"Contents of the manual","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"man/intro.md\", \"man/categories.md\", \"man/spaces.md\", \"man/sectors.md\", \"man/tensors.md\"]\nDepth = 3","category":"page"},{"location":"#Library-outline","page":"Home","title":"Library outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"lib/sectors.md\",\"lib/spaces.md\",\"lib/tensors.md\"]\nDepth = 2","category":"page"},{"location":"man/categories/#s_categories","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The purpose of this page (which can safely be skipped), is to explain how certain concepts and terminology from the theory of monoidal categories apply in the context of tensors. In particular, we are interested in the category mathbfVect, but our concept of tensors can be extended to morphisms of any category that shares similar properties. These properties are reviewed below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, we will as example also study the more general case of mathbfSVect, i.e. the category of super vector spaces, which contains mathbfVect as a subcategory and which are useful to describe fermions.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the end, the goal of identifying tensor manipulations in TensorKit.jl with concepts from category theory is to put the diagrammatic formulation of tensor networks in the most general context on a firmer footing. The following exposition is mostly based on [turaev], combined with input from [selinger], [kassel], [kitaev], and nLab, to which we refer for further information. Furthermore, we recommend the nice introduction of [beer].","category":"page"},{"location":"man/categories/#ss_categoryfunctor","page":"Optional introduction to category theory","title":"Categories, functors and natural transformations","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"To start, a category C consists of","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a class mathrmOb(C) of objects V, W, …\nfor each pair of objects V and W, a set mathrmHom_C(WV) of morphisms fWV; for a given map f, W is called the domain or source, and V the codomain or target.\ncomposition of morphisms fWV and gXW into (f g)XV that is associative, such that for hYX we have f (g h) = (f g) h\nfor each object V, an identity morphism mathrmid_VVV such that f mathrmid_W = f = mathrmid_V f.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The morphisms in mathrmHom_C(VV) are known as endomorphism and this set is also denoted as End_C(V). When the category C is clear, we can drop the subscript in mathrmHom(WV). A morphism fWV is an isomorphism if there exists a morphism f^-1VW called its inverse, such that f^-1 f = mathrmid_W and f f^-1 = mathrmid_V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Throughout this manual, we associate a graphical representation to morphisms and compositions thereof, which is sometimes referred to as the Penrose graphical calculus. To morphisms, we associate boxes with an incoming and outgoing line denoting the object in its source and target. The flow from source to target, and thus the direction of morphism composition f g (sometimes known as the flow of time) can be chosen left to right (like the arrow in fWV), right to left (like the composition order f g, or the matrix product), bottom to top (quantum field theory convention) or top to bottom (quantum circuit convention). Throughout this manual, we stick to this latter convention (which is not very common in manuscripts on category theory):","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: composition)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The direction of the arrows, which become important once we introduce duals, are also subject to convention, and are here chosen to follow the arrow in fWV, i.e. the source comes in and the target goes out. Strangely enough, this is opposite to the most common convention.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of interest, i.e. the category mathbf(Fin)Vect_𝕜 (or some subcategory thereof), the objects are (finite-dimensional) vector spaces over a field 𝕜, and the morphisms are linear maps between these vector spaces with \"matrix multiplication\" as composition. More importantly, the morphism spaces mathrmHom(WV) are themselves vector spaces. More general categories where the morphism spaces are vector spaces over a field 𝕜 (or modules over a ring 𝕜) and the composition of morphisms is a bilinear operation are called 𝕜-linear categories (or 𝕜-algebroids, or mathbfVect_𝕜-enriched categories). In that case, the endomorphisms mathrmEnd(V) are a 𝕜-algebra with mathrmid_V as the identity.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We also introduce some definitions which will be useful further on. A functor F between two categories C and D is, colloquially speaking, a mapping between categories that preserves morphism composition and identities. More specifically, FCD assigns to every object V mathrmOb(C) an object F(V) mathrmOb(D), and to each morphism f mathrmHom_C(WV) a morphism F(f) mathrmHom_D(F(W) F(V)) such that F(f) _D F(g) = F(f _C g) and F(mathrmid_V) = mathrmid_F(V) (where we denoted the possibly different composition laws in C and D explicitly with a subscript). In particular, every category C has an identity functor 1_C that acts trivially on objects and morphisms. Functors can also be composed. A 𝕜-linear functor between two 𝕜-linear categories has a linear action on morphisms.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Given two categories C and D, and two functors F and G that map from C to D, a natural transformation φFG is a family of morphisms φ_V mathrmHom_D(F(V)G(V)) in D, labeled by the objects V of C, such that φ_V F(f) = G(f) φ_W for all morphisms f mathrmHom_C(WV). If all morphisms φ_V are isomorphisms, φ is called a natural isomorphism and the two functors F and G are said to be isomorphic.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The product of two categories C and C, denoted C C, is the category with objects mathrmOb(CC) = mathrmOb(C) mathrmOb(C), whose elements are denoted as tuples (VV), and morphisms mathrmHom_CC((WW) (VV)) = mathrmHom_C(WV) mathrmHom_C(WV). Composition acts as (ff) (gg) = (ff gg) and the identity is given by mathrmid_VV = (mathrmid_V mathrmid_V). In a similar fashion, we can define the product of functors FCD and FCD as a functor FF (CC)(DD) mapping objects (VV) to (F(V) F(V)) and morphisms (ff) to (F(f) F(f)).","category":"page"},{"location":"man/categories/#ss_monoidalcategory","page":"Optional introduction to category theory","title":"Monoidal categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The next property of the category mathbfVect that we want to highlight and generalize is that which allows to take tensor products. Indeed, a category C is said to be a tensor category (a.k.a. a monoidal category), if it has","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a binary operation on objects mathrmOb(C) mathrmOb(C) mathrmOb(C)\na binary operation on morphisms, also denoted as , such that mathrmHom_C(W_1V_1) mathrmHom_C(W_2V_2) mathrmHom_C(W_1 W_2 V_1 V_2)\nan identity or unit object I\nthree families of natural isomorphisms:\n V mathrmOb(C), a left unitor (a.k.a. left unitality constraint) λ_V I V V\n V mathrmOb(C), a right unitor (a.k.a. right unitality constraint) ρ_V V I V\n V_1 V_2 V_3 mathrmOb(C), an associator (a.k.a. associativity constraint) α_V_1V_2V_3(V_1 V_2) V_3 V_1 (V_2 V_3)\nthat satisfy certain consistency conditions (coherence axioms), which are known as the pentagon equation (stating that the two possible mappings from (((V_1 V_2) V_3) V_4) to (V_1 (V_2 (V_3 V_4))) are compatible) and the triangle equation (expressing compatibility between the two possible ways to map ((V_1 I) V_2) to (V_1 (I V_2))).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In terms of functors and natural transformations, is a functor from the product category C C to C. Furthermore, the left (or right) unitor λ (or ρ) is a natural isomorphism between a nameless functor CC that maps objects V I V (or VV I) and the identity functor 1_C. Similarly, the associator α is a natural isomorphism between the two functors ( 1_C) and (1_C ) from C C C to C. In a k-linear category, the tensor product of morphisms is also a bilinear operation. A monoidal category is said to be strict if I V = V = V I and (V_1V_2)V_3 = V_1(V_2V_3), and the left and right unitor and associator are just the identity morphisms for these objects.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For the category mathbfVect, the identity object I is just the scalar field 𝕜 over which the vector spaces are defined, and which can be identified with a one- dimensional vector space. This is not automatically a strict category, especially if one considers how to represent tensor maps on a computer. The distinction between V, I V and V I amounts to adding or removing an extra factor I to the tensor product structure of the domain or codomain, and so the left and right unitor are analogous to removing extra dimensions of size 1 from a multidimensional array. The fact that arrays with and without additional dimensions 1 are not automatically identical and an actual operation is required to insert or remove them, has led to some discussion in several programming languages that provide native support for multidimensional arrays.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For what concerns the associator, the distinction between (V_1 V_2) V_3 and V_1 (V_2 V_3) is typically absent for simple tensors or multidimensional arrays. However, this grouping can be taken to indicate how to build the fusion tree for coupling irreps to a joint irrep in the case of symmetric tensors. As such, going from one to the other requires a recoupling (F-move) which has a non-trivial action on the reduced blocks. We elaborate on this in the context of Fusion categories below. However, we can already note that we will always represent tensor products using a canonical order (((V_1 V_2) V_3) V_N). A similar approach can be followed to turn any tensor category into a strict tensor category (see Section XI.5 of [kassel]).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The different natural isomorphisms involving the unit object have various relations, such as λ_VW α_IVW = λ_V mathrmid_W and λ_I = ρ_I I I I. The last relation defines an isomorphism between I I and I, which can also be used to state that for f g End_C(I), f g = ρ_I (f g) λ_I^-1 = g f. Hence, the tensor product of morphisms in End_C(I) can be related to morphism composition in End_C(I), and furthermore, the monoid of endomorphisms End_C(I) is commutative (abelian). In the case of a 𝕜-linear category, it is an abelian 𝕜-algebra. In the case of mathbfVect, mathrmEnd(I) is indeed isomorphic to the field of scalars 𝕜. We return to the general case where End_C(I) is isomorphic to 𝕜 itself in the section on pre-fusion categories.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Furthermore, Mac Lane's coherence theorem states that the triangle and pentagon condition are sufficient to ensure that any consistent diagram made of associators and left and right unitors (involving all possible objects in C) commutes. For what concerns the graphical notation, the natural isomorphisms will not be represented and we make no distinction between (V_1 V_2) V_3 and V_1 (V_2 V_3). Similarly, the identity object I can be added or removed at will, and when drawn, is often represented by a dotted or dashed line. Note that any consistent way of inserting the associator or left or right unitor to convert a graphical representation to a diagram of compositions and tensor products of morphisms gives rise to the same result, by virtue of Mac Lane's coherence theorem. Using the horizontal direction (left to right) to stack tensor products, this gives rise to the following graphical notation for the tensor product of two morphisms, and for a general morphism t between a tensor product of objects in source and target:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: tensorproduct)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Another relevant example is the category mathbfSVect_𝕜, which has as objects super vector spaces over 𝕜, which are vector spaces with a ℤ₂ grading, i.e. they are decomposed as a direct sum V = V_0 V_1. Furthermore, the morphisms between two super vector spaces are restricted to be grading preserving, i.e. f mathrmHom_mathbfSVect(WV) has f(W_0) V_0 and f(W_1) V_1. The graded tensor product between two super vector spaces is defined as (V_mathrmgW) = (V _mathrmg W)_0 (V _mathrmg W)_1 with (V _mathrmg W)_0 = (V_0 W_0) (V_1 W_1) and (V _mathrmg W)_1 = (V_0 W_1) (V_1 W_0). The unit object I is again isomorphic to 𝕜, i.e. I_0 = 𝕜 and I_1 = 0, a zero-dimensional vector space. In particular, the category mathbfSVect_𝕜 contains mathbfVect_𝕜 as a (monoidal) subcategory, by only selecting those objects V for which V_1 = 0. We will return to the example of mathbfSVect throughout the remainder of this page.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, we generalize the notion of a functor between monoidal categories. A monoidal functor between two tensor categories (C _C I_C α_C λ_C ρ_C) and (D _D I_D α_D λ_D ρ_D) is a functor FCD together with two monoidal constraints, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"a morphism F₀I_D F(I_C);\na natural transformation F_2=F_2(XY) F(X) _D F(Y) F(X _C Y) XY mathrmOb(C) between the functors _D(FF) and F _C from CC to D.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A monoidal natural transformation φ between two monoidal functors FCD and GCDis a natural transformation φFG that furthermore satisfies","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"φ_I_C F_0 = G_0;\n XY mathrmOb(C): φ_X Y F_2(XY) = G_2(XY)(φ_X φ_Y).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For further reference, we also define the following categories which can be associated with the category mathcalC = (C I α λ ρ)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathcalC^mathrmop = (C^mathrmop I α^mathrmop λ^mathrmop ρ^mathrmop) where the opposite category C^mathrmop has the same objects as C but has mathrmHom_C^mathrmop(XY) = mathrmHom_C(YX) and a composition law g ^mathrmop f = f g, with the composition law of C. Furthermore, we have α^mathrmop_XYZ = (α_XYZ)^-1, λ^mathrmop_X = (λ_X)^-1 and ρ^mathrmop_X = (ρ_X)^-1;\nmathcalC^mathrmop = (C ^mathrmop I α^mathrmop λ^mathrmop ρ^mathrmop) where the functor ^mathrmopCC C is the opposite monoidal product, which acts as X ^mathrmop Y = Y X on objects and similar on morphisms. Furthermore, α^mathrmop_XYZ = (α_ZYX)^-1, λ^mathrmop_X = ρ_X and ρ^mathrmop_X = λ_X;\nThe two previous transformations (which commute) composed: mathcalC^mathrmrev = (C^mathrmop ^mathrmop I α^mathrmrev λ^mathrmrev ρ^mathrmrev) with α^mathrmrev_XYZ = α_ZYX, λ^mathrmrev_X = (ρ_X)^-1, ρ^mathrmrev_X = (λ_X)^-1.","category":"page"},{"location":"man/categories/#ss_dual","page":"Optional introduction to category theory","title":"Duality: rigid, pivotal and spherical categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Another property of the category mathbfVect that we want to generalize is the notion of duals. For a vector space V, i.e. an object of mathbfVect, the dual V^* is itself a vector space. Evaluating the action of dual vector on a vector can, because of linearity, be interpreted as a morphism from V^* V to I. Note that elements of a vector space V have no categorical counterpart in themselves, but can be interpreted as morphism from I to V. To map morphisms from mathrmHom(WV) to elements of V W^*, i.e. morphisms in mathrmHom(I V W^*), we use another morphism mathrmHom(I W W^*) which can be considered as the inverse of the evaluation map.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Hence, duality in a monoidal category is defined via an exact paring, i.e. two families of non-degenerate morphisms, the evaluation (or co-unit) ϵ_V ^V V I and the coevaluation (or unit) η_V I V ^V which satisfy the \"snake rules\":","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"ρ_V (mathrmid_V ϵ_V) (η_V mathrmid_V) λ_V^-1 = mathrmid_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"λ_^V^-1 (ϵ_V mathrmid_^V) (mathrmid_^V η_V) ρ_^V^-1 = mathrmid_^V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and can be used to define an isomorphism between mathrmHom(W V U) and mathrmHom(W U ^V) for any triple of objects U V W mathrmOb(C). Note that if there are different duals (with corresponding exact pairings) associated to an object V, a mixed snake composition using the evaluation of one and coevaluation of the other duality can be used to construct an isomorphism between the two associated dual objects. Hence, duality is unique up to isomorphisms.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For (real or complex) vector spaces, we denote the dual as V^*, a notation that we preserve for pivotal categories (see below). Using a bra-ket notation and a generic basis n for V and dual basis m for V^* (such that mn = δ_mn), the evaluation is given by ϵ_V^V V ℂ m n δ_mn and the coevaluation or unit is η_Vℂ V ^Vα α _n n n. Note that this does not require an inner product, i.e. no relation or mapping from n to n was defined. For a general tensor map tW_1 W_2 W_N_2 V_1 V_2 V_N_1, by successively applying η_W_N_2, η_W_N_2-1, …, η_W_1 (in combination with the left or right unitor), we obtain a tensor in V_1 V_2 V_N_1 W_N_2^* W_1^*. Hence, we can define or identify (W_1 W_2 W_N_2)^* = W_N_2^* W_1^*. Indeed, it can be shown that for any category which has duals for objects V and W, an exact pairing between V W and ^W ^V can be constructed out of the evaluation and coevaluation of V and W, such that ^W ^V is at least isomorphic to ^(V W).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Graphically, we represent the exact pairing and snake rules as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: left dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that we denote the dual objects ^V as a line V with arrows pointing in the opposite (i.e. upward) direction. This notation is related to quantum field theory, where anti-particles are (to some extent) interpreted as particles running backwards in time.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"These exact pairings are known as the left evaluation and coevaluation, and ^V is the left dual of V. Likewise, we can also define a right dual V^ of V and associated pairings, the right evaluation tildeϵ_V V V^ I and coevaluation tildeη_V I V^ V, satisfying","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: right dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, one could choose tildeϵ_^V = ϵ_V and thus define V as the right dual of ^V. While there might be other choices, this choice must at least be isomorphic, such that (^V)^ V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If objects V and W have left (respectively right) duals, than for a morphism f mathrmHom(WV), we furthermore define the left (respectively right) transpose ^f mathrmHom(^V ^W) (respectively f^ mathrmHom(V^ W^)) as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: transpose)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where on the right we also illustrate the mapping from t mathrmHom(W_1 W_2 W_3 V_1 V_2) to a morphism in mathrmHom(I V_1 V_2 ^ W_3 ^ W_2 ^ W_1).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the graphical notation, at least the lines with opposite arrows, do not allow to distinguish between the right dual V^ and the left dual ^V. We come back to this point below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A left (or right) duality in a (monoidal) category is now defined as an association of a left (or right) dual with every object of the category, with corresponding exact pairings, and a category admitting such a duality is a left (or right) rigid category (or left or right autonomous category). Given that left (or right) morphism transposition satisfies ^(f g)= ^g ^f= ^f ^mathrmop ^g and recalling ^(V W) = ^W ^V (and similar for right duality), we can define duality in a functorial way. A (left or right) rigid category mathcalC is a category which admits a (left or right) duality functor, i.e. a functor from mathcalC to mathcalC^mathrmrev that maps objects to its (left or right) dual, and morphisms to its (left or right) transpose. In particular, the snake rules can now be read as the functioral requirement that ^(mathrmid_V) = mathrmid_^V.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In all of this, left and right duality can be completely distinct. Equivalently, the left dual of the left dual of an object V, i.e. ^V is not necessarily V itself, nor do the exact pairings enable us to construct an isomorphism between ^V and V. For finite-dimensional vector spaces, however, ^V and V, or thus ^V and V^ are known to be isomorphic. The categorical generalization is that of a pivotal category (or sovereign category), i.e. a monoidal category with two-sided duals X^* = ^X = X^ = X^* such that the left and right duality functor coincide, and thus also the left and right transpose of morphisms, i.e. f^* = ^f = f^ mathrmHom(V^*W^*) for any fmathrmHom(WV). Given that tildeϵ_X and tildeη_X can be interpreted as an exact pairing ϵ_X^* and η_X^*, this can be used to recognize X as a left dual of X^*, which is then not necessarily equal but at least isomorphic to X^** with the isomorphism given by the mixed snake composition alluded to in the beginning of this section, i.e. δ_X X X^** given by δ_X = (tildeϵ_X mathrmid_X^*) (mathrmid_X η_X^*). A more formal statement is that δ is a natural isomorphism between the double dual functor and the identity functor of a category C. In a similar manner, such a δ can be used to define a natural isomorphism between left and right dual functor (which is a slight generalization of the above definition of a pivotal category), and as such it is often called the pivotal structure.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Hence, in a pivotal category, left and right duals are the same or isomorphic, and so are objects and their double duals. As such, we will not distinguish between them in the graphical representation and suppress the natural isomorphism δ. Note, as already suggested by the graphical notation above, that we can interpret transposing a morphism as rotating its graphical notation by 180 degrees (either way).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Furthermore, in a pivotal category, we can define a map from mathrmEnd(V), the endomorphisms of an object V to endomorphisms of the identity object I, i.e. the field of scalars in the case of the category mathbfVect, known as the trace of f. In fact, we can define both a left trace as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr_mathrml(f) = ϵ_V (mathrmid_V^* f) tildeη_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and a right trace as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr_mathrmr(f) = tildeϵ_V (f mathrmid_V^*) η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"They are graphically represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: trace)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and they do not need to coincide. Note that mathrmtr_mathrml(f) = mathrmtr_mathrmr(f*) and that mathrmtr_mathrmlmathrmr(fg) = mathrmtr_mathrmlmathrmr(gf). The (left or right) trace of the identity morphism mathrmid_V defines the corresponding (left or right) dimension of the object V, i.e. mathrmdim_mathrmlmathrmr(V) = tr_mathrmlmathrmr(mathrmid_V). In a spherical category, both definitions of the trace coincide for all V and we simply refer to the trace mathrmtr(f) of an endomorphism. The particular value mathrmdim(V) = mathrmtr(mathrmid_V) is known as the (quantum) dimension of the object V, referred to as dim(V) in TensorKit.jl.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For further information and a more detailed treatment of rigid and pivotal categories, we refer to [turaev] and [selinger]. We conclude this section by studying the example of mathbfSVect. Let us, in every super vector space V, define a basis n that is compatible with the grading, such n=01 indicates that n V_n. We again define a dual basis m for V^* (such that mn = δ_mn), and then define the left evaluation by ϵ_VV^* V ℂ m _mathrmg n mn = δ_mn and the left coevaluation by η_Vℂ V V^*α α _n n _mathrmg n. Note that this does not require an inner product and satisfies the snake rules. For the right evaluation and coevaluation, there are two natural choices, namely tildeϵ_VV V^* ℂ n _mathrmg m (1)^n δ_mn and tildeη_Vℂ V^* V α _n (1)^n n _mathrmg n. The resulting trace of an endomorphism f mathrmEnd(V) is given by mathrmtr^mathrml(f) = mathrmtr^mathrmr(f) = mathrmtr(f) = _n ( 1)^n nfn and is known as either the regular trace (in the case of +1) or the supertrace (in the case of -1). In particular, mathrmdim(V) = mathrmdim(V_0) mathrmdim(V_1), and can be negative in the case of the supertrace. Both are valid choices to make mathbfSVect into a spherical category.","category":"page"},{"location":"man/categories/#ss_braiding","page":"Optional introduction to category theory","title":"Braidings, twists and ribbons","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"While duality and the pivotal structure allow to move vector spaces back and forth between the domain (source) and codomain (target) of a tensor map, reordering vector spaces within the domain or codomain of a tensor map , i.e. within a tensor product V_1 V_2 V_N requires additional structure. In particular, we need at the very least a braided tensor category C, which is endowed with a braiding τ, i.e. a natural isomorphism τ_VWVW WV_VW mathrmOb(C) between the functors and ^mathrmop such that τ_VV(f g) = (g f)τ_WW for any morphisms f mathrmHom(WV) and g mathrmHom(WV). A valid braiding needs to satisfy a coherence condition with the associator α known as the hexagon equation, which expresses that the braiding is -multiplicative, i.e. τ_UVW = (mathrmid_V τ_UW)(τ_UVmathrmid_W) and τ_UVW = (τ_UWmathrmid_VW)(mathrmid_U τ_VW) (where the associator has been omitted). We also have λ_V τ_VI = ρ_VI, ρ_V τ_IV = λ_V and τ_VI = τ_IV^-1 for any V mathrmOb(C).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The braiding isomorphism τ_VW and its inverse are graphically represented as the lines V and W crossing over and under each other:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"such that we have","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding relations)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where the expression on the right hand side, τ_WVτ_VW can generically not be simplified. Hence, for general braidings, there is no unique choice to identify a tensor in VW and WV, as the isomorphisms τ_VW, τ_WV^-1, τ_VW τ_WV τ_VW, … mapping from VW to WV can all be different. In order for there to be a unique map from V_1 V_2 V_N to any permutation of the objects in this tensor product, the braiding needs to be symmetric, i.e. τ_VW = τ_WV^-1 or, equivalently τ_WV τ_VW = mathrmid_VW. The resulting category is then referred to as a symmetric tensor category. In a graphical representation, it means that there is no distinction between over- and under- crossings and, as such, lines can just cross, where the crossing represents the action of τ_VW = τ_WV^-1.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of the category mathbfVect a valid braiding consists of just flipping the the objects/morphisms involved, e.g. for a simple cartesian tensor, permuting the tensor indices is equivalent to applying Julia's function permutedims on the underlying data. Less trivial braiding implementations arise in the context of tensors with symmetries (where the fusion tree needs to be reordered, as discussed in Sectors, representation spaces and fusion trees) or in the case of mathbfSVect, which will again be studied in detail at the end of this section.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The braiding of a space and a dual space also follows naturally, it is given by τ_V^*W = λ_W V^* (ϵ_V mathrmid_W V^*) (mathrmid_V^* τ_VW^-1 mathrmid_V^*) (mathrmid_V^* W η_V) ρ_V^* W^-1, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braiding dual)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Balanced categories C are braided categories that come with a twist θ, a natural transformation from the identity functor 1_C to itself, such that θ_V f = f θ_W for all morphisms f mathrmHom(WV), and for which main requirement is that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_VW = τ_WV (θ_W θ_V) τ_VW = (θ_V θ_W) τ_WV τ_VW","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, a braided pivotal category is balanced, as we can even define two such twists, namely a left and right twist given by","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ^mathrml_V = (ϵ_V mathrmid_V)(mathrmid_V* τ_VV) (tildeη_V mathrmid_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ^mathrmr_V = (mathrmid_V tildeϵ_V)(τ_VV mathrmid_V*)(mathrmid_V ϵ_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where we omitted the necessary left and right unitors and associators. Graphically, the twists and their inverse (for which we refer to [turaev]) are then represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: twists)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The graphical representation also makes it straightforward to verify that (θ^mathrml_V)^* = θ^mathrmr_V^*, (θ^mathrmr_V)^* = θ^mathrml_V^* and mathrmtr_mathrml( θ^mathrmr_V ) = mathrmtr_mathrmr( θ^mathrml_V ).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"When θ^mathrml = θ^mathrmr, or thus, equivalently, θ_V^* = θ_V^* for either θ^mathrml or θ^mathrmr, the category is said to be tortile or also a ribbon category, because its graphical representation is compatible with the isotopy of a ribbon, i.e. where the lines representing objects are depicted as ribbons. For convenience, we continue to denote them as lines. Ribbon categories are necessarily spherical, i.e. one can prove the equivalence of the left and right trace.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Alternatively, one can start from a balanced and rigid category (e.g. with a left duality), and use the twist θ, which should satisfy θ_V^* = θ_V^*, to define a pivotal structure, or, to define the exact pairing for the right dual functor as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeη_V = τ_VV^* (θ_V mathrmid_V^*) η_V = (mathrmid_V^* θ_V) τ_VV^* η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeϵ_V = ϵ_V (mathrmid_V^* θ_V) τ_VV^* = ϵ_V τ_VV^* (θ_V mathrmid_V^*)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: pivotal from twist)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where we have drawn θ as θ^mathrml on the left and as θ^mathrmr on the right, but in this case the starting assumption was that they are one and the same, and we defined the pivotal structure so as to make it compatible with the graphical representation. This construction of the pivotal structure can than be used to define the trace, which is spherical, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmtr(f) = ϵ_V τ_VV^* (( θ_V f) mathrmid_V^*) η_V = ϵ_V (mathrmid_V^* (f θ_V)) τ_VV^* η_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note finally, that a ribbon category where the braiding is symmetric, is known as a compact closed category. For a symmetric braiding, the trivial twist θ_V = mathrmid_V is always a valid choice, but it might not be the choice that one necessarily want to use. Let us study the case of mathbfSVect again. Reinvoking our basis m V and n W, the braiding τ_VW is given by the Koszul sign rule, i.e. τ_VWm _mathrmg n (-1)^m n n _mathrmg m. Hence, braiding amounts to flipping the two spaces, but picks up an additional minus sign if both m V_1 and n W_1. This braiding is symmetric, i.e. τ_WV τ_VW = mathrmid_VW. Between spaces and dual spaces, we similarly obtain the braiding rule m _mathrmg n (-1)^m n n _mathrmg m. Combining the braiding and the pivotal structure gives rise to a ribbon category, and thus, a compact closed category, where the resulting twist is given by θ_V n (1)^n n for tildeϵ_VV V^* ℂ n _mathrmg m (1)^n δ_mn and corresponding tildeη_V. Hence, if the right (co)evaluation contains a minus sign, the twist is θ_V = mathrmid_V, which, as mentioned above, is always a valid twist for a symmetric category. However, if the right (co)evaluation contains no minus sign, the twist acts as the parity endomorphism, i.e. as +1 on V_0 and as -1 on V_1, which, as we will see in the next section, corresponds to a choice bearing additional structure.","category":"page"},{"location":"man/categories/#ss_adjoints","page":"Optional introduction to category theory","title":"Adjoints and dagger categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A final aspect of categories as they are relevant to physics, and in particular quantum physics, is the notion of an adjoint or dagger. A dagger category C is a category together with an involutive functor CC^mathrmop, i.e. it acts as the identity on objects, whereas on morphisms fWV it defines a morphism f^VW such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmid_V^ = mathrmid_V\n(f g)^ = f^ ^mathrmop g^ = g^ f^\n(f^)^ = f","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Sometimes also the symbol * is used instead of , however we have already used * to denote dual objects and transposed morphisms in the case of a pivotal category.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If the category is ℂ-linear, the dagger functor is often assumed to be antilinear, i.e. (λ f)^ = barλ f^ for λ ℂ and f mathrmHom(VW). In a dagger category, a morphism fWV is said to be unitary if it is an isomorphism and f^-1 = f^. Furthermore, an endomorphism fVV is hermitian or self-adjoint if f^ = f. Finally, we will also use the term isometry for a morphism fWV which has a left inverse f^, i.e. such that f^ f = mathrmid_W, but for which f f^ is not necessarily the identity (but rather some orthogonal projector, i.e. a hermitian idempotent in mathrmEnd(V)).","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the graphical representation, the dagger of a morphism can be represented by mirroring the morphism around a horizontal axis, and then reversing all arrows (bringing them back to their original orientation before the mirror operation):","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: dagger)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where for completeness we have also depicted the graphical representation of the transpose, which is a very different operation. In particular, the dagger does not reverse the order of the tensor product. Note that, for readibility, we have not mirrored or rotated the label in the box, but this implies that we need to use a type of box for which the action of mirroring or rotating can be observed.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A dagger monoidal category is one in which the associator and left and right unitor are unitary morphisms. Similarly, a dagger braided category also has a unitary braiding, and a dagger balanced category in addition has a unitary twist.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"There is more to be said about the interplay between the dagger and duals. Given a left evaluation ϵ_V V^* V I and coevaluation η_V I V V^*, we can define a right evaluation tildeϵ_V = (η_V)^ and coevaluation tildeη_V = (ϵ_V)^. Hence, left rigid dagger categories are automatically pivotal dagger categories.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The (right) twist defined via the pivotal structure now becomes","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_V = (mathrmid_V (η_V)^) (τ_VV mathrmid_V^*) (mathrmid_V η_V)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and is itself unitary. Even for a symmetric category, the twist defined as such must not be the identity, as we discuss for the mathbfSVect example below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, the dagger allows to define two Hermitian forms on the morphisms, namely f g _mathrmlmathrmr = mathrmtr_mathrmlmathrmr(f^ g), which coincide for a spherical category. For a unitary 𝕜-linear category, these Hermitian forms should be positive definite and thus define an inner product on each of the homomorphism spaces mathrmHom(WV). In particular then, dimensions of objects are positive, as they satisfy mathrmdim_mathrmlmathrmr(V) = mathrmid_V mathrmid_V _mathrmlmathrmr.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"This concludes the most important categorical definitions and structures that we want to discuss for the category mathbfVect, but which can also be realized in other categories. In particular, the interface of TensorKit.jl could in principle represent morphisms from any 𝕜-linear monoidal category, but assumes categories with duals to be pivotal and in fact spherical, and categories with a braiding to be ribbon categories. A dagger ribbon category where the braiding is symmetric, i.e. a dagger category which is also a compact closed category and where the right (co)evaluation is given via the dagger of the left (co)evaluation is called a dagger compact category. This is the playground of quantum mechanics of bosonic and fermionic systems. However, we also allow for non- symmetric braiding in TensorKit.jl, though this functionality is currently much more limited.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Again studying the category mathbfSVect_ℂ (now explicitly over the complex numbers) and using the conventional adjoint or the complex Euclidean inner product to define the dagger functor, the right (co)evaluation that is obtained from applying the dagger to the left (co)evaluation is the definition we gave above with the +1 sign. This choice gives rise to a regular trace (versus the supertrace) of endomorphisms, to positive dimensions, and a non-trivial twist that acts as the parity endomorphism. The resulting category is then a dagger compact category, that can be used for the quantum mechanical description of fermionic systems. The bosonic version is obtained by restricting to the subcategory mathbfVect.","category":"page"},{"location":"man/categories/#ss_fusion","page":"Optional introduction to category theory","title":"Direct sums, simple objects and fusion categories","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"These last two section on fusion categories is also applicable, in a straightforward manner, to mathbfVect and mathbfSVect, but is rather meant to provide the background of working with symmetries. We first need two new concepts:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"An object W mathrmOb(C) is a direct sum of objects V_1 V_2 V_k mathrmOb(C) if there exists a family morphisms x_α mathrmHom(V_αW) and y^α mathrmHom(WV_α) such that mathrmid_W = _α=1^k x_α y^α and y^α x_β = δ^α_β mathrmid_V_α. The morphisms x_α and y^α are known as inclusions and projections respectively, and in the context of dagger categories it is natural to assume y^α = x_α^ in order to obtain an orthogonal direct sum decomposition.\nA simple object V mathrmOb(C) of a 𝕜-linear category C is an object for which End_C(V) 𝕜, i.e. the algebra of endomorphisms on V is isomorphic to the field (or ring) 𝕜. As End_C(V) always contains the identity morphism mathrmid_V, and this must be the only linearly independent endomorphism if V is a simple object, the isomorphism between mathrmEnd_C(V) and 𝕜 is typically of the form k 𝕜 k mathrmid_V End_C(V). In particular, for mathbfSVect and its subcategory mathbfVect, the unit object I is a simple object.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In particular, for a pivotal 𝕜-linear category where I is simple, it holds that the left and right dimensions of any simple object V are invertible in 𝕜, and that any endomorphism f mathrmEnd(V) can be written as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"f = (mathrmdim_mathrml(V))^-1 mathrmtr_mathrml(f) mathrmid_V = (mathrmdim_mathrmr(V))^-1 mathrmtr_mathrmr(f) mathrmid_V","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Strictly speaking, this holds only if the category is non-degenerate, which means that I is simple and that any non-degenerate pairing eV W I induces a non- degenerate pairing mathrmHom(IV) mathrmHom(IW) mathrmEnd(I). This property is always satisfied for a pre-fusion category C, i.e. a monoidal 𝕜- linear category having a set mathcalS mathrmOb(C) of simple objects mathcalS=I V_1 V_2 ldots such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"the monoidal unit I_C mathcalS;\nmathrmHom_C(V_iV_j) = 0 (the singleton set containing only the zero homomorphism) for any distinct V_i V_j mathcalS;\nevery object V mathrmOb(C) can be written as a direct sum of a finite family of elements from mathcalS.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that in the direct sum decomposition of an object V, a particular simple object V_i might appear multiple times. This number is known as the multiplicity index N^V_i, and equal to the rank of mathrmHom(VV_i) or, equivalently, of mathrmHom(V_iV). Hence, we can choose inclusion and projection maps x_iμV_iV and y^iμVV_i for μ = 1ldots N^V_i, such that mathrmid_V = sum_isum_μ=1^N_V^i x_iμ y^iμ and y^iμ x_jν = δ^i_j δ^μ_ν. In particular, for a simple object V, it either appears in mathcalS or is isomorphic to an object S. We thus have N^V_i = 1 for one particular object V_i and N^V_j= 0 for all other j, with x_i and y^i = (x_i)^-1 representing the isomorphism between V and V_i.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The homomorphisms between two general objects W and V in a pre-fusion category can be decomposed as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmHom(WV) _V_i mathcalS mathrmHom(WV_i) mathrmHom(V_iV)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and thus that the rank of mathrmHom(WV) is given by _i N^W_i N^V_i.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A fusion category is a pre-fusion category that has (left or right) duals, i.e. that is rigid, and that only has a finite number of isomorphism classes of simple objects. Note that the duality functor maps mathrmEnd(V) to mathrmEnd(V^*), such that, if V is a simple object, so must be V^*. Henceforth, we will be sloppy about the distinction between a pre-fusion or fusion category, only use the latter term, even when it is not fully justified.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Before continuing, let us use some examples to sketch the relevance of the concept of fusion categories. As mentioned, the categories mathbfVect_𝕜 and mathbfSVect_𝕜 have I 𝕜 as simple object. For mathbfVect, this is the only simple object, i.e. any other vector space V over 𝕜, can be thought of as a direct sum over N^V_I = mathrmdim(V) multiple copies of 𝕜. In mathbfSVect, the object J = 0 𝕜 with J_0=0 the zero dimensional space and J_1 𝕜 is another simple object. Clearly, there are no non-zero grading preserving morphisms between I and J, i.e. mathrmHom(IJ) = 0, whereas mathrmHom(JJ) 𝕜. Any other super vector space V=V_0 V_1 can be written as a direct sum over N^V_I = mathrmdim(V_0) copies of I and N^V_J = mathrmdim(V_1) copies of J.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A more representative example is that of the category C = mathbfRep_mathsfG, the category of representations of a group mathsfG. Colloquially, this could be thought of as a subcategory of mathbfVect containing as objects vector spaces V on which a representation of mathsfG is defined, denoted as u_V(g) for g mathsfG, and as morphisms the equivariant transformations, i.e. intertwiners between the representations on the source and target:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"mathrmHom_C(WV) = f mathrmHom_mathbfVect(WV) u_V(g) f = f u_W(g) g G","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the u_V(g) is itself generally not an element from End_C(V). Simple objects V_a are those corresponding irreducible representations (irreps) a of the group mathsfG, for which Schur's lemma implies End_C(V_a) 𝕜 and mathrmHom_C(V_a V_b) = 0 if a and b are not equivalent irreps. On the dual space V^*, the group acts with the contragradient representation, i.e. u_V^*(g) = ((u_V(g))^-1)^* = u_V(g^-1)^*, where one should remind that ^* denotes the transpose. For a finite group or compact Lie group, we can introduce a dagger and restrict to unitary representations, such that u_V(g)^-1 = u_V(g)^ and the contragradient representation becomes the complex conjugated representation, denoted as u_V^*(g) = baru_V(g). The resulting category can then be given the structure of a unitary ribbon (pre-)fusion category. (Note that the number of isomorphism classes of simple objects, i.e. the number of non-equivalent irreps, is finite only in the case of a finite group). This example is very relevant to working with symmetries in TensorKit.jl, and will be expanded upon in more detail below.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Fusion categories have a number of simplifying properties. A pivotal fusion category is spherical as soon as mathrmdim_mathrml(V_i) = mathrmdim_mathrmr(V_i) (i.e. the trace of the identity morphism) for all (isomorphism classes of) simple objects (note that all isomorphic simple objects have the same dimension). A braided pivotal fusion category is spherical if and only if it is a ribbon category.","category":"page"},{"location":"man/categories/#ss_topologicalfusion","page":"Optional introduction to category theory","title":"Topological data of a unitary pivotal fusion category","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"More explicitly, the different structures (monoidal structure, duals and pivotal structure, braiding and twists) in a fusion category can be characterized in terms of the simple objects, which we will henceforth denoted with just a instead of V_a. This gives rise to what is known as the topological data of a unitary pivotal fusion category, most importantly the N, F and R symbols, which are introduced in this final section.","category":"page"},{"location":"man/categories/#Monoidal-structure","page":"Optional introduction to category theory","title":"Monoidal structure","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Starting with the monoidal or tensor product, we start by characterizing how the object a b can be decomposed as a direct sum over simple objects c, which gives rise to the multiplicity indices N_c^ab, as well as the inclusion maps, which we henceforth denote as X_cμ^abcab for μ=1N^c_ab. In the context of a unitary fusion category, on which we now focus, the corresponding projection maps are Y^cμ_ab = (X_cμ^ab)^abc such that","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(X_cμ^ab)^ X_cμ^ab = δ_cc δ_μμ mathrmid_c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Graphically, we represent these relations as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: fusion)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and also refer to the inclusion and projection maps as splitting and fusion tensor, respectively.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For both (ab)c and a(bc), which are isomorphic via the associator α_abc, we must thus obtain a direct sum decomposition with the same multiplicity indices, leading to the associativity constraint","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"N_d^abc= _e N_e^ab N_d^ec = _f N_f^bc N_d^af","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The corresponding inclusion maps can be chosen as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"X_d(eμν)^abc = (X_eμ^ab mathrmid_c) X_dν^ec d(ab)c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"tildeX_d(fκλ)^abc = (mathrmid_a X_fκ^bc) X_dλ^af da(bc)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and satisfy","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(X_d(eμν)^abc)^ X_d(eμν)^abc = δ_ee δ_μμ δ_νν δ_dd mathrmid_d","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"_deμν X_d(eμν)^abc (X_d(eμν)^abc)^ = mathrmid_(ab)c","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and similar for tildeX_d(fκλ)^abc. Applying the associator leads to a relation","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"α_abc X_d(eμν)^abc = _fκλ F^abc_d_(eμν)^(fκλ) tildeX_d(fκλ)^abc","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"which defines the F-symbol, i.e. the matrix elements of the associator","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(tildeX_d(fκλ)^abc)^ α_abc X_d(eμν)^abc = δ_dd F^abc_d_(eμν)^(fκλ) mathrmid_d","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Note that the left hand side represents a map in mathrmHom(dd), which must be zero if d is different from d, hence the δ_dd on the right hand side. In a strict category, or in the graphical notation, the associator α is omitted and these relations thus represent a unitary basis transform between the basis of inclusion maps X_d(eμν)^abc and tildeX_d(fκλ)^abc, which is also called an F-move, i.e. graphically:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Fmove)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The matrix F^abc_d is thus a unitary matrix. The pentagon coherence equation can also be rewritten in terms of these matrix elements, and as such yields the celebrated pentagon equation for the F-symbols. In a similar fashion, the unitors result in N^a1_b = N^1a_b = δ^a_b (where we have now written 1 instead of I for the unit object) and the triangle equation leads to additional relations between the F- symbols involving the unit object. In particular, if we identify X^1a_a1a(1a) with λ_a^ and X^a1_a1a(a1) with ρ_a^, the triangle equation and its collaries imply that F^1ab_c_(11μ)^(cν1) = δ^ν_μ, and similar relations for F^a1b_c and F^ab1_c, which are graphically represented as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Fmove1)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"In the case of group representations, i.e. the category mathbfRep_mathsfG, the splitting and fusion tensors are known as the Clebsch-Gordan coefficients, especially in the case of mathsfSU_2. An F-move amounts to a recoupling and the F-symbols can thus be identified with the 6j-symbols (strictly speaking, Racah's W-symbol for mathsfSU_2).","category":"page"},{"location":"man/categories/#Duality-and-pivotal-structure","page":"Optional introduction to category theory","title":"Duality and pivotal structure","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Next up is duality. Since we are assuming a dagger category, it can be assumed pivotal, where the left dual objects are identical to the right dual objects, and the left and right (co)evaluation are related via the dagger. We have already pointed out above that the dual object a^* of a simple object a is simple, and thus, it must be isomorphic to one of the representives bara of the different isomorphism classes of simple objects that we have chosen. Note that it can happen that bara=a. Duality implies an isomorphism between mathrmHom(WV) and mathrmHom(IVW^*), and thus, for a simple object a, mathrmEnd(a) 𝕜 is isomorphic to mathrmHom(1aa^*), such that the latter is also isomorphic to 𝕜, or thus N^abara_1 = 1. Also, all possible duals of a must be isomorphic, and thus there is a single representive bara, meaning that N^ab_1 = δ^bbara, i.e. for all other b bara, mathrmHom(1ab) mathrmHom(b^*a) = 0. Note that also barbara=a.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Let us now be somewhat careful with respect to the isomorphism between a^* and bara. If bara a, we can basically choose the representative of that isomorphism class as bara = a^*. However, this choice might not be valid if bara=a, as in that case the choice is already fixed, and might be different from a. To give a concrete example, the j=12 representation of mathsfSU_2 has a dual (contragradient, but because of unitarity, complex conjugated) representation which is isomorphic to itself, but not equal. In the context of tensors in quantum physics, we would like to be able to represent this representation and its conjugate, so we need to take the distinction and the isomorphism between them into account. This means that mathrmHom(a^*bara) is isomorphic to 𝕜 and contains a single linearly independent element, Z_a, which is a unitary isomorphism such that Z_a^dagger Z_a = mathrmid_a^* and Z_a Z_a^dagger = mathrmid_bara. Using the transpose, we obtain Z_a^* mathrmHom(bara^*a), and thus it is proportional to Z_bara, i.e. Z_a^* = χ_a Z_bara with χ_a a complex phase (assuming 𝕜 = ℂ). Another transpose results in Z_bara^* = χ_bara Z_a with χ_bara = overlineχ_a, where bar of a scalar quantity denotes its complex conjugate to avoid confusion with the transpose functor. If aand bara are distinct, we can essentially choose Z_bara such that χ_a is 1. However, for a=bara, the value of χ_a cannot be changed, but must satisfy χ_a^2 = 1, or thus χ_a = 1. This value is a topological invariant known as the Frobenius-Schur indicator. Graphically, we represent this isomorphism and its relations as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Zisomorphism)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We can now discuss the relation between the exact pairing and the fusion and splitting tensors. Given that the (left) coevaluation η_a mathrmHom(1 aa^*), we can define the splitting tensor as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"X^abara_1 = frac1sqrtd_a(mathrmid_a Z_a) η_a = frac1sqrtd_a(Z_a^* mathrmid_bara) tildeη_bara mathrmHom(1 abara)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The prefactor takes care of normalization, i.e. with η_a^ = tildeϵ_a, we find η_a^ η_a = tildeϵ_a η_a = mathrmtr(mathrmid_a) = d_a mathrmid_1, and thus (X^abara_1)^ X^abara_1 = mathrmid_1. Here, we have denoted d_a = mathrmdim(a) = mathrmtr(mathrmid_a) for the quantum dimension of the simple objects a. With this information, we can then compute F^abaraa_a, which has a single element (it's a 1 1 matrix), and find F^abaraa_a = fracχ_ad_a, where we've used tildeη_a = ϵ_a^ and the snake rules. Hence, both the quantum dimensions and the Frobenius-Schur indicator are encoded in the F-symbol. Hence, they do not represent new independent data. Again, the graphical representation is more enlightning:","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: ZtoF)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"With these definitions, we can now also evaluate the action of the evaluation map on the splitting tensors, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: splittingfusionrelation)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"where again bar denotes complex conjugation in the second line, and we introduced two new families of matrices A^ab_c and B^ab_c, whose entries are composed out of entries of the F-symbol, namely","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"A^ab_c^nu_mu = sqrtfracd_a d_bd_c χ_bara overlineF^baraab_b_(111)^(cμν)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"and","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"B^ab_c^nu_mu = sqrtfracd_a d_bd_c F^abbarb_a^(111)_(cμν)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Composing the left hand side of first graphical equation with its dagger, and noting that the resulting element f mathrmEnd(a) must satisfy f = d_a^-1 mathrmtr(f) mathrmid_a, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: Brelation)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"allows to conclude that _ν B^ab_c^ν_μ overlineB^ab_c^ν_μ = delta_μμ, i.e. B^ab_c is a unitary matrix. The same result follows for A^ab_c in analogue fashion.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"note: Note\nIn the context of fusion categories, one often resorts to the so-called isotopic normalization convention, where splitting tensors are normalized as (X^ab_cμ)^ X^ab_cmu = sqrtfracd_a d_bd_c δ_cc δ_μμ mathrmid_c. This kills some of the quantum dimensions in formulas like the ones above and essentially allows to rotate the graphical notation of splitting and fusion tensors (up to a unitary transformation). Nonetheless, for our implementation of tensors and manipulations thereof (in particular orthonormal factorizations such as the singular value decomposition), we find it more convenient to work with the original normalization convention.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Let us again study in more detail the example mathbfRep_mathsfG. The quantum dimension d_a of an irrep a is just the normal vector space dimension (over 𝕜) of the space on which the irrep acts. The dual of an irrep a is its contragradient representation, which in the case of unitary representations amounts to the complex conjugate representation. This representation can be isomorphic to an already defined irrep bara, for example a itself. If that happens, it does not automatically imply that the irrep a is real-valued. For example, all irreps of mathsfSU_2 are self- dual, with the isomorphism given by a π rotation over the y-axis (in the standard basis). The resulting Frobenius-Schur indicator is +1 for integer spin irreps, and -1 for half-integer spin irreps. The value χ_a=+1 indicates that the representation can be made real, e.g. the integer spin representations can be written as tensor representations of mathsfSO_3 by a change of basis. The value χ_a=-1 indicates that the representation is quaternionic and cannot be made real.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The (co)evaluation expresses that the standard contraction of a vector with a dual vector yields a scalar, i.e. a representation and its dual (the contragradient) yields the trivial representation when correctly contracted. The coevaluation together with the isomorphism between the conjugate of irrep a and some irrep bara yields a way to define the Clebsch-Gordan coefficients (i.e. the splitting and fusion tensor) for fusing a bara to the trivial irrep, i.e. to what is called a singlet in the case of mathsfSU_2.","category":"page"},{"location":"man/categories/#Braidings-and-twists","page":"Optional introduction to category theory","title":"Braidings and twists","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Finally, we can study the braiding structure of a pivotal fusion category. Not all fusion categories have a braiding structure. The existence of a braiding isomorphism τ_VWVWWV requires at the very least that N^ab_c = N^ba_c at the level of the simple objects. We can then express τ_ab in terms of its matrix elements as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"τ_ab X^ab_cμ = _ν R^ab_c^ν_μ X^ba_cν","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: braidingR)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The hexagon coherence axiom for the braiding and the associator can then be reexpressed in terms of the F-symbols and R-symbols.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"We can now compute the twist, which for simple objects needs to be scalars (or in fact complex phases because of unitarity) multiplying the identity morphism, i.e.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"θ_a = mathrmid_a sum_bμ fracd_bd_a R^aa_b^μ_μ","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"or graphically","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: simpletwist)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"Henceforth, we reserve θ_a for the scalar value itself. Note that θ_a = θ_bara as our category is spherical and thus a ribbon category, and that the defining relation of a twist implies","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"R^ba_c^κ_μ R^ab_c^μ_ν = fractheta_cθ_a θ_b δ^κ_ν","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"If a = bara, we can furthermore relate the twist, the braiding and the Frobenius- Schur indicator via θ_a χ_a R^aa_1 =1, because of","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"(Image: twistfrobeniusschur)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"For the recurring example of mathbfRep_mathsfG, the braiding acts simply as the swap of the two vector spaces on which the representations are acting and is thus symmetric, i.e. τ_ba τ_ab = mathrmid_ab. All the twists are simply θ_a = 1. For an irrep that is self-dual, i.e. bara=a, the final expression simplifies to R^aa_1 = χ_a and thus states that the fusion from a a to the trivial sector is either symmetric under swaps if χ_a=1 or antisymmetric if χ_a=-1. For the case of mathsfSU_2, the coupling of two spin j states to a singlet it symmetric for integer j and odd for half-integer j.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"With this, we conclude our exposition of unitary fusion categories. There are many fusion categories that do not originate from the representation theory of groups, but are related to quantum groups and the representation theory of quasi-triangular Hopf algebras. They have non-integer quantum dimensions and generically admit for braidings which are not symmetric. A particular class of interesting fusion categories are modular fusion categories, which provide the mathematical structure for the theory of anyons and topological sectors in topological quantum states of matter. Thereto, one defines the modular S matrix, defined as","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"S_ab = frac1D mathrmtr(τ_ab τ_ba) = frac1D _c N^ab_c d_c fracθ_cθ_a θ_b","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"The normalization constant is given by D = sqrtsum_a d_a^2, and thus truly requires a fusion category with a finite number of (isomorphism classes of) simple objects. For a modular fusion category, the symmetric matrix S is non-degenerate, and in fact (for a unitary fusion category) unitary. Note, however, that for a symmetric braiding S_ab = fracd_a d_bD and thus S is a rank 1 matrix. In particular, mathbfRep_mathsfG is never a modular category and the properties associated with this are not of (direct) importance for TensorKit.jl. We refer to the references for further information about modular categories.","category":"page"},{"location":"man/categories/#Bibliography","page":"Optional introduction to category theory","title":"Bibliography","text":"","category":"section"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[turaev]: Turaev, V. G., & Virelizier, A. (2017). Monoidal categories and topological field theory (Vol. 322).\n Birkhäuser.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[selinger]: Selinger, P. (2010). A survey of graphical languages for monoidal categories.\n In New structures for physics (pp. 289-355). Springer, Berlin, Heidelberg.\n [https://arxiv.org/abs/0908.3347](https://arxiv.org/abs/0908.3347)","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[kassel]: Kassel, C. (2012). Quantum groups (Vol. 155).\n Springer Science & Business Media.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[kitaev]: Kitaev, A. (2006). Anyons in an exactly solved model and beyond.\n Annals of Physics, 321(1), 2-111.","category":"page"},{"location":"man/categories/","page":"Optional introduction to category theory","title":"Optional introduction to category theory","text":"[beer]: From categories to anyons: a travelogue\n Kerstin Beer, Dmytro Bondarenko, Alexander Hahn, Maria Kalabakov, Nicole Knust, Laura Niermann, Tobias J. Osborne, Christin Schridde, Stefan Seckmeyer, Deniz E. Stiegemann, and Ramona Wolf\n [https://arxiv.org/abs/1811.06670](https://arxiv.org/abs/1811.06670)","category":"page"}] }