From 1349fd3c77c19e2e234d6fd83fa8c12ff34be2ac Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:23:04 +0100 Subject: [PATCH] Docs update I (#101) * Add missing docstring `ProductSector` and export * Add docstring `Trivial` * Update docs fusiontrees * add docstrings random functions * add docstrings of exported methods to docs * small typos * Docs: update lib/tensors to reflect exported functions * Docs: add docstrings * Update exports * Fix docstring typos * Incorporate comments and suggestions --- docs/src/index.md | 2 +- docs/src/lib/sectors.md | 49 +++++----- docs/src/lib/tensors.md | 92 +++++++++++++++++-- docs/src/man/tensors.md | 2 +- src/TensorKit.jl | 8 +- src/auxiliary/random.jl | 23 +++++ src/fusiontrees/manipulations.jl | 19 +++- src/sectors/product.jl | 7 ++ src/sectors/sectors.jl | 13 ++- src/tensors/abstracttensor.jl | 149 +++++++++++++++++++++++++++++-- src/tensors/tensor.jl | 5 ++ 11 files changed, 323 insertions(+), 46 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index ba20bdde..e232eb7a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -40,6 +40,6 @@ Depth = 3 ## Library outline ```@contents -Pages = ["lib/spaces.md","lib/sectors.md","lib/tensors.md"] +Pages = ["lib/sectors.md","lib/spaces.md","lib/tensors.md"] Depth = 2 ``` diff --git a/docs/src/lib/sectors.md b/docs/src/lib/sectors.md index 3907b58a..80cfdf02 100644 --- a/docs/src/lib/sectors.md +++ b/docs/src/lib/sectors.md @@ -1,4 +1,4 @@ -# Symmetry sectors an fusion trees +# Symmetry sectors and fusion trees ```@meta CurrentModule = TensorKit @@ -11,10 +11,12 @@ SectorValues FusionStyle BraidingStyle AbstractIrrep +Trivial ZNIrrep U1Irrep SU2Irrep CU1Irrep +ProductSector FermionParity FibonacciAnyon FusionTree @@ -42,27 +44,32 @@ TensorKit.vertex_ind2label ⊠(::Sector, ::Sector) ``` -## Methods for manipulating fusion trees or pairs of fusion-splitting trees -The main method for manipulating a fusion-splitting tree pair is -```@docs -braid(f1::FusionTree{G}, f2::FusionTree{G}, levels1::IndexTuple, levels2::IndexTuple, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {G<:Sector,N₁,N₂} -``` -which, for `FusionStyle(G) isa SymmetricBraiding`, simplifies to -```@docs -permute(f1::FusionTree{G}, f2::FusionTree{G}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {G<:Sector,N₁,N₂} -``` -These operations are implemented by composing the following more elementary manipulations +## Methods for manipulating fusion trees + +For manipulating single fusion trees, the following internal methods are defined: ```@docs -braid(f::FusionTree{G,N}, levels::NTuple{N,Int}, p::NTuple{N,Int}) where {G<:Sector, N} -permute(f::FusionTree{G,N}, p::NTuple{N,Int}) where {G<:Sector, N} -TensorKit.repartition -TensorKit.artin_braid +insertat +split +merge +elementary_trace +planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃} +artin_braid +braid(f::FusionTree{I,N}, levels::NTuple{N,Int}, p::NTuple{N,Int}) where {I<:Sector,N} +permute(f::FusionTree{I,N}, p::NTuple{N,Int}) where {I<:Sector,N} ``` -Finally, there are some additional manipulations for internal use + +These can be composed to manipulate fusion-splitting tree pairs, for which the following +methods are defined: + ```@docs -TensorKit.insertat -TensorKit.split -TensorKit.merge +bendright +bendleft +foldright +foldleft +cycleclockwise +cycleanticlockwise +repartition +transpose +braid(f₁::FusionTree{I}, f₂::FusionTree{I}, levels1::IndexTuple, levels2::IndexTuple, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector,N₁,N₂} +permute(f₁::FusionTree{I}, f₂::FusionTree{I}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector,N₁,N₂} ``` diff --git a/docs/src/lib/tensors.md b/docs/src/lib/tensors.md index 9355dcf3..219cfa8a 100644 --- a/docs/src/lib/tensors.md +++ b/docs/src/lib/tensors.md @@ -6,11 +6,23 @@ CurrentModule = TensorKit ## Type hierarchy +The type hierarchy of tensors is as follows: + ```@docs AbstractTensorMap AbstractTensor TensorMap AdjointTensorMap +BraidingTensor +``` + +Some aliases are provided for convenience: + +```@docs +AbstractTensor +Tensor +TrivialTensorMap +TrivialTensor ``` ## Specific `TensorMap` constructors @@ -22,20 +34,86 @@ unitary isometry ``` +Additionally, several special-purpose methods exist to generate data according to specific distributions: + +```@docs +randuniform +randnormal +randisometry +``` + +## Accessing properties and data + +The following methods exist to obtain type information: + +```@docs +spacetype +sectortype +storagetype +tensormaptype +``` + +To obtain information about the indices, you can use: +```@docs +domain +codomain +space +numin +numout +numind +codomainind +domainind +allind +``` + +To obtain information about the data, the following methods exist: +```@docs +blocksectors +blockdim +block +blocks +fusiontrees +hasblock +``` + ## `TensorMap` operations +The operations that can be performed on a `TensorMap` can be organized into *index +manipulations*, *(planar) traces* and *(planar) contractions*. + +### 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. + ```@docs -permute(t::TensorMap{S}, p1::IndexTuple, p2::IndexTuple) where {S} -permute! -braid -braid! +permute(t::AbstractTensorMap{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}; copy::Bool=false) where {S,N₁,N₂} +braid(t::AbstractTensorMap{S}, (p₁, p₂)::Index2Tuple, levels::IndexTuple; copy::Bool=false) where {S} +transpose twist +``` +```@docs +permute!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, p::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} +braid! +transpose! twist! -add! -trace! -contract! +``` +```@docs +add_permute! +add_braid! +add_transpose! +add_transform! ``` +### Traces and contractions + +```@docs +trace_permute! +contract! +``` ## `TensorMap` factorizations diff --git a/docs/src/man/tensors.md b/docs/src/man/tensors.md index 1b4eb452..897a7711 100644 --- a/docs/src/man/tensors.md +++ b/docs/src/man/tensors.md @@ -346,7 +346,7 @@ as the internal structure of the representation space when the corresponding sec abelian sectors are `Irrep[SU₂]` and `Irrep[CU₁]`, for which the internal structure is the natural one. -There are some tools available to facilate finding the proper range of sector `c` in space +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 ```@repl tensors diff --git a/src/TensorKit.jl b/src/TensorKit.jl index a81aabe3..281b5509 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -13,6 +13,7 @@ export FusionStyle, UniqueFusion, MultipleFusion, MultiplicityFreeFusion, SimpleFusion, GenericFusion export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic, NoBraiding export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep +export ProductSector export FermionParity, FermionNumber, FermionSpin export FibonacciAnyon, IsingAnyon @@ -50,9 +51,8 @@ export fℤ₂, fU₁, fSU₂ export ℤ₂Space, ℤ₃Space, ℤ₄Space, U₁Space, CU₁Space, SU₂Space # tensor maps -export domain, codomain, numind, numout, numin, spacetype, storagetype, scalartype -export domainind, codomainind, allind -export tensormaptype +export domain, codomain, numind, numout, numin, domainind, codomainind, allind +export spacetype, sectortype, storagetype, scalartype, tensormaptype export blocksectors, blockdim, block, blocks # random methods for constructor @@ -71,7 +71,7 @@ export leftorth, rightorth, leftnull, rightnull, leftorth!, rightorth!, leftnull!, rightnull!, tsvd!, tsvd, eigen, eigen!, eig, eig!, eigh, eigh!, exp, exp!, isposdef, isposdef!, ishermitian, sylvester -export braid!, permute!, transpose!, twist! +export braid, braid!, permute, permute!, transpose, transpose!, twist, twist! export catdomain, catcodomain export OrthogonalFactorizationAlgorithm, QR, QRpos, QL, QLpos, LQ, LQpos, RQ, RQpos, diff --git a/src/auxiliary/random.jl b/src/auxiliary/random.jl index d40c16f6..90e72ccf 100644 --- a/src/auxiliary/random.jl +++ b/src/auxiliary/random.jl @@ -1,9 +1,32 @@ +""" + randuniform([::Type{T}=Float64], dims::Dims{N}) -> Array{T,N} + +Create an array of size `dims` with random entries uniformly distributed in the allowed +values of `T`. + +See also [`randnormal`](@ref), [`randisometry`](@ref) and[`randhaar`](@ref). +""" randuniform(dims::Base.Dims) = randuniform(Float64, dims) randuniform(::Type{T}, dims::Base.Dims) where {T<:Number} = rand(T, dims) +""" + randnormal([::Type{T}=Float64], dims::Dims{N}) -> Array{T,N} + +Create an array of size `dims` with random entries distributed according to the standard normal distribution. + +See also [`randuniform`](@ref), [`randisometry`](@ref) and[`randhaar`](@ref). +""" randnormal(dims::Base.Dims) = randnormal(Float64, dims) randnormal(::Type{T}, dims::Base.Dims) where {T<:Number} = randn(T, dims) +""" + randisometry([::Type{T}=Float64], dims::Dims{2}) -> Array{T,2} + randhaar([::Type{T}=Float64], dims::Dims{2}) -> Array{T,2} + +Create a random isometry of size `dims`, uniformly distributed according to the Haar measure. + +See also [`randuniform`](@ref) and [`randnormal`](@ref). +""" randisometry(dims::Base.Dims{2}) = randisometry(Float64, dims) function randisometry(::Type{T}, dims::Base.Dims{2}) where {T<:Number} return dims[1] >= dims[2] ? _leftorth!(randnormal(T, dims), QRpos(), 0)[1] : diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index cea269fa..dc857025 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -605,6 +605,14 @@ function planar_trace(f₁::FusionTree{I}, f₂::FusionTree{I}, return newtrees end +""" + 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. +""" function planar_trace(f::FusionTree{I,N}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃} u = one(I) @@ -652,6 +660,13 @@ function planar_trace(f::FusionTree{I,N}, end # trace two neighbouring indices of a single fusion tree +""" + 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. +""" function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} (N > 1 && 1 <= i <= N) || throw(ArgumentError("Cannot trace outputs i=$i and i+1 out of only $N outputs")) @@ -745,7 +760,7 @@ end # -> manipulations that depend on a braiding # -> requires both Fsymbol and Rsymbol """ - artin_braid(f::FusionTree, i; inv::Bool = false) -> <:AbstractDict{typeof(t), <:Number} + 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 @@ -755,7 +770,7 @@ The keyword `inv` determines whether index `i` will braid above or below index ` 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`. +if `BraidingStyle(sectortype(f)) isa SymmetricBraiding`. """ function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} 1 <= i < N || diff --git a/src/sectors/product.jl b/src/sectors/product.jl index 33f90e31..c99bd762 100644 --- a/src/sectors/product.jl +++ b/src/sectors/product.jl @@ -2,6 +2,13 @@ #==============================================================================# const SectorTuple = Tuple{Vararg{Sector}} +""" + ProductSector{T<:SectorTuple} + +Represents 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`](@ref) (`⊠`) operator on the components. +""" struct ProductSector{T<:SectorTuple} <: Sector sectors::T end diff --git a/src/sectors/sectors.jl b/src/sectors/sectors.jl index d633bda3..132e728f 100644 --- a/src/sectors/sectors.jl +++ b/src/sectors/sectors.jl @@ -62,16 +62,21 @@ Base.eltype(::Type{SectorValues{I}}) where {I<:Sector} = I Base.values(::Type{I}) where {I<:Sector} = SectorValues{I}() # Define a sector for ungraded vector spaces -struct Trivial <: Sector -end +""" + Trivial + +Singleton 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. +""" +struct Trivial <: Sector end Base.show(io::IO, ::Trivial) = print(io, "Trivial()") Base.IteratorSize(::Type{SectorValues{Trivial}}) = HasLength() Base.length(::SectorValues{Trivial}) = 1 Base.iterate(::SectorValues{Trivial}, i=false) = return i ? nothing : (Trivial(), true) function Base.getindex(::SectorValues{Trivial}, i::Int) - return i == 1 ? Trivial() : - throw(BoundsError(values(Trivial), i)) + return i == 1 ? Trivial() : throw(BoundsError(values(Trivial), i)) end findindex(::SectorValues{Trivial}, c::Trivial) = 1 diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index f69a2970..8462efdd 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -23,15 +23,60 @@ i.e. a tensor map with only a non-trivial output space. const AbstractTensor{S<:IndexSpace,N} = AbstractTensorMap{S,N,0} # tensor characteristics +#------------------------ Base.eltype(::Union{T,Type{T}}) where {T<:AbstractTensorMap} = scalartype(T) + +""" + spacetype(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Type{S<:IndexSpace} + +Return the type of the elementary space `S` of a tensor. +""" spacetype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = S + +""" + sectortype(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Type{I<:Sector} + +Return the type of sector `I` of a tensor. +""" sectortype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = sectortype(S) + function InnerProductStyle(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} return InnerProductStyle(S) end + +""" + field(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Type{𝕂<:Field} + +Return the type of field `𝕂` of a tensor. +""" field(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = field(S) + +""" + numout(::Union{T,Type{T}}) where {T<: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`](@ref) and [`numind`](@ref). +""" numout(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ + +""" + numin(::Union{T,Type{T}}) where {T<: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`](@ref) and [`numind`](@ref). +""" numin(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₂ + +""" + 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`](@ref) and [`numin`](@ref). +""" numind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ + N₂ function similarstoragetype(TT::Type{<:AbstractTensorMap}, ::Type{T}) where {T} @@ -51,28 +96,80 @@ similarstoragetype(t::AbstractTensorMap, T) = similarstoragetype(typeof(t), T) const order = numind -# tensormap implementation should provide codomain(t) and domain(t) +@doc """ + codomain(t::AbstractTensorMap{S,N₁,N₂}, [i::Int]) -> ProductSpace{S,N₁} + +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`](@ref) and [`space`](@ref). +""" codomain + codomain(t::AbstractTensorMap, i) = codomain(t)[i] +target(t::AbstractTensorMap) = codomain(t) # categorical terminology + +@doc """ + domain(t::AbstractTensorMap{S,N₁,N₂}, [i::Int]) -> ProductSpace{S,N₂} + +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`](@ref) and [`space`](@ref). +""" domain + domain(t::AbstractTensorMap, i) = domain(t)[i] source(t::AbstractTensorMap) = domain(t) # categorical terminology -target(t::AbstractTensorMap) = codomain(t) # categorical terminology + +""" + space(t::AbstractTensorMap{S,N₁,N₂}, [i::Int]) -> HomSpace{S,N₁,N₂} + +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. +""" space(t::AbstractTensorMap) = HomSpace(codomain(t), domain(t)) space(t::AbstractTensorMap, i::Int) = space(t)[i] + +""" + 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. +""" dim(t::AbstractTensorMap) = dim(space(t)) -# some index manipulation utilities +""" + codomainind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Tuple{Int} + +Return all indices of the codomain of a tensor. + +See also [`domainind`](@ref) and [`allind`](@ref). +""" function codomainind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} return ntuple(n -> n, N₁) end +codomainind(t::AbstractTensorMap) = codomainind(typeof(t)) + +""" + domainind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Tuple{Int} + +Return all indices of the domain of a tensor. + +See also [`codomainind`](@ref) and [`allind`](@ref). +""" function domainind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} return ntuple(n -> N₁ + n, N₂) end +domainind(t::AbstractTensorMap) = domainind(typeof(t)) + +""" + allind(::Union{T,Type{T}}) where {T<:AbstractTensorMap} -> Tuple{Int} + +Return all indices of a tensor, i.e. the indices of its domain and codomain. + +See also [`codomainind`](@ref) and [`domainind`](@ref). +""" function allind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} return ntuple(n -> n, N₁ + N₂) end - -codomainind(t::AbstractTensorMap) = codomainind(typeof(t)) -domainind(t::AbstractTensorMap) = domainind(typeof(t)) allind(t::AbstractTensorMap) = allind(typeof(t)) function adjointtensorindex(::AbstractTensorMap{<:IndexSpace,N₁,N₂}, i) where {N₁,N₂} @@ -87,6 +184,46 @@ function adjointtensorindices(t::AbstractTensorMap, p::Index2Tuple) return adjointtensorindices(t, p[1]), adjointtensorindices(t, p[2]) end +@doc """ + blocks(t::AbstractTensorMap) + +Return an iterator over all blocks of a tensor, i.e. all coupled sectors and their corresponding blocks. + +See also [`block`](@ref), [`blocksectors`](@ref), [`blockdim`](@ref) and [`hasblock`](@ref). +""" blocks + +@doc """ + block(t::AbstractTensorMap, c::Sector) + +Return the block of a tensor corresponding to a coupled sector `c`. + +See also [`blocks`](@ref), [`blocksectors`](@ref), [`blockdim`](@ref) and [`hasblock`](@ref). +""" block + +""" + hasblock(t::AbstractTensorMap, c::Sector) -> Bool + +Verify whether a tensor has a block corresponding to a coupled sector `c`. +""" hasblock + +@doc """ + blocksectors(t::AbstractTensorMap) + +Return an iterator over all coupled sectors of a tensor. +""" blocksectors + +@doc """ + blockdim(t::AbstractTensorMap, c::Sector) -> Base.Dims + +Return the dimensions of the block of a tensor corresponding to a coupled sector `c`. +""" blockdim + +@doc """ + fusiontrees(t::AbstractTensorMap) + +Return an iterator over all splitting - fusion tree pairs of a tensor. +""" fusiontrees + # Equality and approximality #---------------------------- function Base.:(==)(t1::AbstractTensorMap, t2::AbstractTensorMap) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 5c815867..2415e419 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -39,7 +39,12 @@ end const Tensor{S<:IndexSpace,N,I<:Sector,A,F₁,F₂} = TensorMap{S,N,0,I,A,F₁,F₂} const TrivialTensorMap{S<:IndexSpace,N₁,N₂,A<:DenseMatrix} = TensorMap{S,N₁,N₂,Trivial,A, Nothing,Nothing} +""" + tensormaptype(::Type{S}, N₁::Int, N₂::Int, [::Type{T}]) where {S<:IndexSpace,T} -> ::Type{<:TensorMap} +Return the fully specified type of a tensor map with elementary space `S`, `N₁` output +spaces and `N₂` input spaces, either with scalar type `T` or with storage type `T`. +""" function tensormaptype(::Type{S}, N₁::Int, N₂::Int, ::Type{T}) where {S,T} I = sectortype(S) if T <: DenseMatrix