diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index 87822d59..dbe1a534 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -27,6 +27,7 @@ function CartesianSpace(dims::AbstractDict; kwargs...) throw(SectorMismatch(msg)) end end +CartesianSpace(g::Base.Generator; kwargs...) = CartesianSpace(g...; kwargs...) field(::Type{CartesianSpace}) = ℝ InnerProductStyle(::Type{CartesianSpace}) = EuclideanProduct() diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index a9929f9a..d266744b 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -28,6 +28,7 @@ function ComplexSpace(dims::AbstractDict; kwargs...) throw(SectorMismatch(msg)) end end +ComplexSpace(g::Base.Generator; kwargs...) = ComplexSpace(g...; kwargs...) field(::Type{ComplexSpace}) = ℂ InnerProductStyle(::Type{ComplexSpace}) = EuclideanProduct() diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index abcfb5ef..c1c4e6af 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -115,3 +115,22 @@ function dim(W::HomSpace) end return d end + +# Operations on HomSpaces +# ----------------------- +function permute(W::HomSpace{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} + cod = ProductSpace{S,N₁}(map(n -> W[n], p₁)) + dom = ProductSpace{S,N₂}(map(n -> dual(W[n]), p₂)) + return cod ← dom +end + +""" + 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`. +""" +function compose(W::HomSpace{S}, V::HomSpace{S}) where {S} + domain(W) == codomain(V) || throw(SpaceMismatch("$(domain(W)) ≠ $(codomain(V))")) + return HomSpace(codomain(W), domain(V)) +end diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index b2b7a454..4155e743 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -160,6 +160,7 @@ individual spaces `V₁`, `V₂`, ..., or the spaces contained in `P`. """ function fuse end fuse(V::ElementarySpace) = isdual(V) ? flip(V) : V +fuse(V::ElementarySpace, W::ElementarySpace) = fuse(promote(V, W)...) function fuse(V₁::VectorSpace, V₂::VectorSpace, V₃::VectorSpace...) return fuse(fuse(fuse(V₁), fuse(V₂)), V₃...) end diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index dccb38a2..0c202245 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -30,19 +30,19 @@ To permute into an existing destination, see [permute!](@ref) and [`add_permute! """ function permute(t::AbstractTensorMap{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}; copy::Bool=false) where {S,N₁,N₂} - cod = ProductSpace{S,N₁}(map(n -> space(t, n), p₁)) - dom = ProductSpace{S,N₂}(map(n -> dual(space(t, n)), p₂)) + space′ = permute(space(t), (p₁, p₂)) # share data if possible if !copy if p₁ === codomainind(t) && p₂ === domainind(t) return t elseif has_shared_permute(t, (p₁, p₂)) - return TensorMap(reshape(t.data, dim(cod), dim(dom)), cod, dom) + return TensorMap(reshape(t.data, dim(codomain(space′)), dim(domain(space′))), + codomain(space′), domain(space′)) end end # general case @inbounds begin - return permute!(similar(t, cod ← dom), t, (p₁, p₂)) + return permute!(similar(t, space′), t, (p₁, p₂)) end end function permute(t::AdjointTensorMap{S}, (p₁, p₂)::Index2Tuple; @@ -118,10 +118,9 @@ function braid(t::AbstractTensorMap{S}, (p₁, p₂)::Index2Tuple, levels::Index return t end # general case - cod = ProductSpace{S}(map(n -> space(t, n), p₁)) - dom = ProductSpace{S}(map(n -> dual(space(t, n)), p₂)) + space′ = permute(space(t), (p₁, p₂)) @inbounds begin - return braid!(similar(t, cod ← dom), t, (p₁, p₂), levels) + return braid!(similar(t, space′), t, (p₁, p₂), levels) end end # TODO: braid for `AdjointTensorMap`; think about how to map the `levels` argument. @@ -171,10 +170,9 @@ function LinearAlgebra.transpose(t::AbstractTensorMap{S}, return t end # general case - cod = ProductSpace{S}(map(n -> space(t, n), p₁)) - dom = ProductSpace{S}(map(n -> dual(space(t, n)), p₂)) + space′ = permute(space(t), (p₁, p₂)) @inbounds begin - return transpose!(similar(t, cod ← dom), t, (p₁, p₂)) + return transpose!(similar(t, space′), t, (p₁, p₂)) end end @@ -313,10 +311,7 @@ function add_transform!(tdst::AbstractTensorMap{S,N₁,N₂}, β::Number, backend::Backend...) where {S,N₁,N₂} @boundscheck begin - all(i -> space(tsrc, p₁[i]) == space(tdst, i), 1:N₁) || - throw(SpaceMismatch("source = $(codomain(tsrc))←$(domain(tsrc)), - dest = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) - all(i -> space(tsrc, p₂[i]) == space(tdst, N₁ + i), 1:N₂) || + permute(space(tsrc), (p₁, p₂)) == space(tdst) || throw(SpaceMismatch("source = $(codomain(tsrc))←$(domain(tsrc)), dest = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) end @@ -357,7 +352,7 @@ end function _add_abelian_block!(tdst, tsrc, p, fusiontreetransform, f₁, f₂, α, β, backend...) (f₁′, f₂′), coeff = first(fusiontreetransform(f₁, f₂)) - TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, β, backend...) + @inbounds TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, β, backend...) return nothing end @@ -375,8 +370,8 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen else for (f₁, f₂) in fusiontrees(tsrc) for ((f₁′, f₂′), coeff) in fusiontreetransform(f₁, f₂) - TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, true, - backend...) + @inbounds TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, + true, backend...) end end end @@ -388,7 +383,8 @@ function _add_nonabelian_sector!(tdst, tsrc, p, fusiontreetransform, s₁, s₂, for (f₁, f₂) in fusiontrees(tsrc) (f₁.uncoupled == s₁ && f₂.uncoupled == s₂) || continue for ((f₁′, f₂′), coeff) in fusiontreetransform(f₁, f₂) - TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, true, backend...) + @inbounds TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, true, + backend...) end end return nothing diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 4678b780..46bbb285 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -18,10 +18,18 @@ Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(scalartype(t)) / α) LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real=2) = scale!(t, inv(norm(t, p))) LinearAlgebra.normalize(t::AbstractTensorMap, p::Real=2) = scale(t, inv(norm(t, p))) -function Base.:*(t1::AbstractTensorMap, t2::AbstractTensorMap) +""" + compose(t1::AbstractTensorMap, t2::AbstractTensorMap) -> AbstractTensorMap + +Return the `AbstractTensorMap` that implements the composition of the two tensor maps `t1` +and `t2`. +""" +function compose(t1::AbstractTensorMap, t2::AbstractTensorMap) return mul!(similar(t1, promote_type(scalartype(t1), scalartype(t2)), - codomain(t1) ← domain(t2)), t1, t2) + compose(space(t1), space(t2))), t1, t2) end +Base.:*(t1::AbstractTensorMap, t2::AbstractTensorMap) = compose(t1, t2) + Base.exp(t::AbstractTensorMap) = exp!(copy(t)) function Base.:^(t::AbstractTensorMap, p::Integer) return p < 0 ? Base.power_by_squaring(inv(t), -p) : Base.power_by_squaring(t, p) @@ -242,10 +250,9 @@ end function LinearAlgebra.mul!(tC::AbstractTensorMap, tA::AbstractTensorMap, tB::AbstractTensorMap, α=true, β=false) - if !(codomain(tC) == codomain(tA) && domain(tC) == domain(tB) && - domain(tA) == codomain(tB)) + compose(space(tA), space(tB)) == space(tC) || throw(SpaceMismatch("$(space(tC)) ≠ $(space(tA)) * $(space(tB))")) - end + for c in blocksectors(tC) if hasblock(tA, c) # then also tB should have such a block A = block(tA, c) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 9f8ed5a9..a2b6f171 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -59,9 +59,7 @@ end function TO.tensoradd_structure(pC::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, conjA::Symbol) where {S,N₁,N₂} if conjA == :N - cod = ProductSpace{S,N₁}(space.(Ref(A), pC[1])) - dom = ProductSpace{S,N₂}(dual.(space.(Ref(A), pC[2]))) - return dom → cod + return permute(space(A), pC) else return TO.tensoradd_structure(adjointtensorindices(A, pC), adjoint(A), :N) end @@ -128,12 +126,9 @@ function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, pA::Index2Tuple, conjA, B::AbstractTensorMap{S}, pB::Index2Tuple, conjB) where {S,N₁,N₂} - spaces1 = TO.flag2op(conjA).(space.(Ref(A), pA[1])) - spaces2 = TO.flag2op(conjB).(space.(Ref(B), pB[2])) - spaces = (spaces1..., spaces2...) - cod = ProductSpace{S,N₁}(getindex.(Ref(spaces), pC[1])) - dom = ProductSpace{S,N₂}(dual.(getindex.(Ref(spaces), pC[2]))) - return dom → cod + sA = TO.tensoradd_structure(pA, A, conjA) + sB = TO.tensoradd_structure(pB, B, conjB) + return permute(compose(sA, sB), pC) end function TO.checkcontractible(tA::AbstractTensorMap{S}, iA::Int, conjA::Symbol, @@ -165,10 +160,7 @@ function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @boundscheck begin - all(i -> space(tsrc, p₁[i]) == space(tdst, i), 1:N₁) || - throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) - all(i -> space(tsrc, p₂[i]) == space(tdst, N₁ + i), 1:N₂) || + space(tdst) == permute(space(tsrc), (p₁, p₂)) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), tdst = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) all(i -> space(tsrc, q₁[i]) == dual(space(tsrc, q₂[i])), 1:N₃) || diff --git a/test/spaces.jl b/test/spaces.jl index e2171d0a..5b591bca 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -410,5 +410,8 @@ println("------------------------------------") @test W[5] == V5' @test @constinferred(hash(W)) == hash(deepcopy(W)) != hash(W') @test W == deepcopy(W) + @test W == @constinferred permute(W, ((1, 2), (3, 4, 5))) + @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') + @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TensorKit.compose(W, W') end end