Skip to content
Merged
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Random = "1"
SafeTestsets = "0.1"
ScopedValues = "1.3.0"
Strided = "2"
TensorKitSectors = "0.3.3"
TensorKitSectors = "0.3.5"
TensorOperations = "5.1"
Test = "1"
TestExtras = "0.2,0.3"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/lib/tensors.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ type `add_transform!`, for additional expert-mode options that allows for additi
scaling, as well as the selection of a custom backend.

```@docs
permute(::AbstractTensorMap, ::Index2Tuple{N₁,N₂}) where {N₁,N₂}
permute(::AbstractTensorMap, ::Index2Tuple)
braid(::AbstractTensorMap, ::Index2Tuple, ::IndexTuple)
transpose(::AbstractTensorMap, ::Index2Tuple)
repartition(::AbstractTensorMap, ::Int, ::Int)
Expand Down
3 changes: 1 addition & 2 deletions src/planar/planaroperations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ function planartrace!(
α::Number, β::Number,
backend, allocator
)
(S = spacetype(C)) == spacetype(A) ||
throw(SpaceMismatch("incompatible spacetypes"))
S = check_spacetype(C, A)
if BraidingStyle(sectortype(S)) == Bosonic()
return trace_permute!(C, A, (p₁, p₂), (q₁, q₂), α, β, backend)
end
Expand Down
26 changes: 19 additions & 7 deletions src/spaces/vectorspaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -376,19 +376,31 @@ abstract type CompositeSpace{S <: ElementarySpace} <: VectorSpace end
InnerProductStyle(::Type{<:CompositeSpace{S}}) where {S} = InnerProductStyle(S)

"""
spacetype(a) -> Type{S<:IndexSpace}
spacetype(::Type) -> Type{S<:IndexSpace}
spacetype(a) -> Type{S <: IndexSpace}
spacetype(::Type) -> Type{S <: IndexSpace}

Return the type of the elementary space `S` of object `a` (e.g. a tensor). Also works in
type domain.
Return the type of the elementary space `S` of object `a` (e.g. a tensor).
Also works in type domain.
"""
spacetype(x) = spacetype(typeof(x))
function spacetype(::Type{T}) where {T}
throw(MethodError(spacetype, (T,)))
end
spacetype(::Type{T}) where {T} = throw(MethodError(spacetype, (T,)))
spacetype(::Type{E}) where {E <: ElementarySpace} = E
spacetype(::Type{S}) where {E, S <: CompositeSpace{E}} = E

"""
check_spacetype(Bool, x, y, z...) -> Bool
check_spacetype(x, y, z...) -> Type{<:IndexSpace}

Check whether the given inputs have matching spacetypes.
The first signature returns a `Bool` to indicate whether all spacetypes are equal,
while the second will return the spacetype if all types are equal, and throw a [`SpaceMismatch`](@ref) if not.
"""
check_spacetype(::Type{Bool}, x, y, z...) = _allequal(spacetype, (x, y, z...))
@noinline function check_spacetype(x, y, z...)
check_spacetype(Bool, x, y, z...) || throw(SpaceMismatch("incompatible space types"))
return spacetype(x)
end

# make ElementarySpace instances behave similar to ProductSpace instances
blocksectors(V::ElementarySpace) = collect(sectors(V))
blockdim(V::ElementarySpace, c::Sector) = dim(V, c)
Expand Down
1 change: 0 additions & 1 deletion src/tensors/abstracttensor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ similarstoragetype(::Type{D}, ::Type{T}) where {D <: AbstractDict{<:Sector, <:Ab
# default storage type for numbers
similarstoragetype(::Type{T}) where {T <: Number} = Vector{T}


# tensor characteristics: space and index information
#-----------------------------------------------------
"""
Expand Down
18 changes: 13 additions & 5 deletions src/tensors/diagonal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,10 @@ function TO.tensorcontract_type(
B::DiagonalTensorMap, ::Index2Tuple{1, 1}, ::Bool,
::Index2Tuple{1, 1}
)
M = similarstoragetype(A, TC)
M == similarstoragetype(B, TC) ||
throw(ArgumentError("incompatible storage types:\n$(M) ≠ $(similarstoragetype(B, TC))"))
spacetype(A) == spacetype(B) || throw(SpaceMismatch("incompatible space types"))
return DiagonalTensorMap{TC, spacetype(A), M}
S = check_spacetype(A, B)
TC′ = promote_permute(TC, sectortype(S))
M = promote_storagetype(similarstoragetype(A, TC′), similarstoragetype(B, TC′))
return DiagonalTensorMap{TC, S, M}
end

function TO.tensoralloc(
Expand All @@ -291,6 +290,15 @@ function Base.zero(d::DiagonalTensorMap)
return DiagonalTensorMap(zero.(d.data), d.domain)
end

function compose_dest(A::DiagonalTensorMap, B::DiagonalTensorMap)
S = check_spacetype(A, B)
TC = TO.promote_contract(scalartype(A), scalartype(B), One)
M = promote_storagetype(similarstoragetype(A, TC), similarstoragetype(B, TC))
TTC = DiagonalTensorMap{TC, S, M}
structure = codomain(A) ← domain(B)
return TO.tensoralloc(TTC, structure, Val(false))
end

function LinearAlgebra.mul!(
dC::DiagonalTensorMap, dA::DiagonalTensorMap, dB::DiagonalTensorMap, α::Number, β::Number
)
Expand Down
113 changes: 58 additions & 55 deletions src/tensors/indexmanipulations.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Index manipulations
#---------------------

# find the scalartype after applying operations: take into account fusion and/or braiding
# might need to become Float or Complex to capture complex recoupling coefficients but don't alter precision
for (operation, manipulation) in (
:flip => :sector, :twist => :braiding,
:transpose => :fusion, :permute => :sector, :braid => :sector,
)
promote_op = Symbol(:promote_, operation)
manipulation_scalartype = Symbol(manipulation, :scalartype)

@eval begin
$promote_op(t::AbstractTensorMap) = $promote_op(typeof(t))
$promote_op(::Type{T}) where {T <: AbstractTensorMap} =
$promote_op(scalartype(T), sectortype(T))
$promote_op(::Type{T}, ::Type{I}) where {T <: Number, I <: Sector} =
sectorscalartype(I) <: Integer ? T :
sectorscalartype(I) <: Real ? float(T) : complex(T)
# TODO: currently the manipulations all use sectorscalartype, change to:
# $manipulation_scalartype(I) <: Integer ? T :
# $manipulation_scalartype(I) <: Real ? float(T) : complex(T)
end
end

"""
flip(t::AbstractTensorMap, I) -> t′::AbstractTensorMap

Expand All @@ -15,7 +38,7 @@ Return a new tensor that is isomorphic to `t` but where the arrows on the indice
"""
function flip(t::AbstractTensorMap, I; inv::Bool = false)
P = flip(space(t), I)
t′ = similar(t, P)
t′ = similar(t, promote_flip(t), P)
for (f₁, f₂) in fusiontrees(t)
(f₁′, f₂′), factor = only(flip(f₁, f₂, I; inv))
scale!(t′[f₁′, f₂′], t[f₁, f₂], factor)
Expand All @@ -39,53 +62,36 @@ See [`permute`](@ref) for creating a new tensor and [`add_permute!`](@ref) for a
end

"""
permute(tsrc::AbstractTensorMap, (p₁, p₂)::Index2Tuple;
copy::Bool=false)
-> tdst::TensorMap
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.
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!](@ref) and [`add_permute!`](@ref)
"""
function permute(
t::AbstractTensorMap, (p₁, p₂)::Index2Tuple{N₁, N₂}; copy::Bool = false
) where {N₁, N₂}
space′ = permute(space(t), (p₁, p₂))
# share data if possible
if !copy && p₁ === codomainind(t) && p₂ === domainind(t)
return t
end
# general case
@inbounds begin
return permute!(similar(t, space′), t, (p₁, p₂))
end
end
function permute(t::TensorMap, (p₁, p₂)::Index2Tuple{N₁, N₂}; copy::Bool = false) where {N₁, N₂}
space′ = permute(space(t), (p₁, p₂))
function permute(t::AbstractTensorMap, p::Index2Tuple; copy::Bool = false)
# share data if possible
if !copy
if p === codomainind(t) && p₂ === domainind(t)
if p == (codomainind(t), domainind(t))
return t
elseif has_shared_permute(t, (p₁, p₂))
return TensorMap(t.data, space)
elseif t isa TensorMap && has_shared_permute(t, p)
return TensorMap(t.data, permute(space(t), p))
end
end

# general case
@inbounds begin
return permute!(similar(t, space′), t, (p₁, p₂))
end
tdst = similar(t, promote_permute(t), permute(space(t), p))
return @inbounds permute!(tdst, t, p)
end
function permute(t::AdjointTensorMap, (p₁, p₂)::Index2Tuple; copy::Bool = false)
p₁′ = adjointtensorindices(t, p₂)
p₂′ = adjointtensorindices(t, p₁)
return adjoint(permute(adjoint(t), (p₁′, p₂′); copy))
end
function permute(t::AbstractTensorMap, p::IndexTuple; copy::Bool = false)
return permute(t, (p, ()); copy)
end
permute(t::AbstractTensorMap, p::IndexTuple; copy::Bool = false) = permute(t, (p, ()); copy)

function has_shared_permute(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple)
return (p₁ === codomainind(t) && p₂ === domainind(t))
Expand Down Expand Up @@ -143,20 +149,16 @@ If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwis
To braid into an existing destination, see [braid!](@ref) and [`add_braid!`](@ref)
"""
function braid(
t::AbstractTensorMap, (p₁, p₂)::Index2Tuple, levels::IndexTuple; copy::Bool = false
t::AbstractTensorMap, p::Index2Tuple, levels::IndexTuple; copy::Bool = false
)
@assert length(levels) == numind(t)
if BraidingStyle(sectortype(t)) isa SymmetricBraiding
return permute(t, (p₁, p₂); copy = copy)
end
if !copy && p₁ == codomainind(t) && p₂ == domainind(t)
return t
end
length(levels) == numind(t) || throw(ArgumentError("invalid levels"))

BraidingStyle(sectortype(t)) isa SymmetricBraiding && return permute(t, p; copy)
(!copy && p == (codomainind(t), domainind(t))) && return t

# general case
space′ = permute(space(t), (p₁, p₂))
@inbounds begin
return braid!(similar(t, space′), t, (p₁, p₂), levels)
end
tdst = similar(t, promote_braid(t), permute(space(t), p))
return @inbounds braid!(tdst, t, p, levels)
end
# TODO: braid for `AdjointTensorMap`; think about how to map the `levels` argument.

Expand Down Expand Up @@ -196,20 +198,15 @@ If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwis
To permute into an existing destination, see [permute!](@ref) and [`add_permute!`](@ref)
"""
function LinearAlgebra.transpose(
t::AbstractTensorMap, (p₁, p₂)::Index2Tuple = _transpose_indices(t);
t::AbstractTensorMap, p::Index2Tuple = _transpose_indices(t);
copy::Bool = false
)
if sectortype(t) === Trivial
return permute(t, (p₁, p₂); copy = copy)
end
if !copy && p₁ == codomainind(t) && p₂ == domainind(t)
return t
end
sectortype(t) === Trivial && return permute(t, p; copy)
(!copy && p == (codomainind(t), domainind(t))) && return t

# general case
space′ = permute(space(t), (p₁, p₂))
@inbounds begin
return transpose!(similar(t, space′), t, (p₁, p₂))
end
tdst = similar(t, promote_transpose(t), permute(space(t), p))
return @inbounds transpose!(tdst, t, p)
end

function LinearAlgebra.transpose(
Expand Down Expand Up @@ -296,6 +293,10 @@ function twist!(t::AbstractTensorMap, inds; inv::Bool = false)
throw(ArgumentError(msg))
end
has_shared_twist(t, inds) && return t

(scalartype(t) <: Real && !(sectorscalartype(sectortype(t)) <: Real)) &&
throw(ArgumentError("No in-place `twist!` for a real tensor with complex sector type"))

N₁ = numout(t)
for (f₁, f₂) in fusiontrees(t)
θ = prod(i -> i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i - N₁]), inds)
Expand All @@ -317,7 +318,9 @@ See [`twist!`](@ref) for storing the result in place.
"""
function twist(t::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false)
!copy && has_shared_twist(t, inds) && return t
return twist!(Base.copy(t), inds; inv)
tdst = similar(t, promote_twist(t))
copy!(tdst, t)
return twist!(tdst, inds; inv)
end

# Methods which change the number of indices, implement using `Val(i)` for type inference
Expand Down Expand Up @@ -413,7 +416,7 @@ end
spacecheck_transform(f, tdst::AbstractTensorMap, tsrc::AbstractTensorMap, args...) =
spacecheck_transform(f, space(tdst), space(tsrc), args...)
@noinline function spacecheck_transform(f, Vdst::TensorMapSpace, Vsrc::TensorMapSpace, p::Index2Tuple)
spacetype(Vdst) == spacetype(Vsrc) || throw(SectorMismatch("incompatible sector types"))
check_spacetype(Vdst, Vsrc)
f(Vsrc, p) == Vdst ||
throw(
SpaceMismatch(
Expand All @@ -427,7 +430,7 @@ spacecheck_transform(f, tdst::AbstractTensorMap, tsrc::AbstractTensorMap, args..
return nothing
end
@noinline function spacecheck_transform(::typeof(braid), Vdst::TensorMapSpace, Vsrc::TensorMapSpace, p::Index2Tuple, levels::IndexTuple)
spacetype(Vdst) == spacetype(Vsrc) || throw(SectorMismatch("incompatible sector types"))
check_spacetype(Vdst, Vsrc)
braid(Vsrc, p, levels) == Vdst ||
throw(
SpaceMismatch(
Expand Down
28 changes: 13 additions & 15 deletions src/tensors/linalg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real = 2) = scale!(t, inv(norm
LinearAlgebra.normalize(t::AbstractTensorMap, p::Real = 2) = scale(t, inv(norm(t, p)))

# destination allocation for matrix multiplication
# note that we don't fall back to `tensoralloc_contract` since that needs to account for
# permutations, which might require complex scalartypes even if the inputs are real.
function compose_dest(A::AbstractTensorMap, B::AbstractTensorMap)
S = check_spacetype(A, B)
TC = TO.promote_contract(scalartype(A), scalartype(B), One)
pA = (codomainind(A), domainind(A))
pB = (codomainind(B), domainind(B))
pAB = (codomainind(A), ntuple(i -> i + numout(A), numin(B)))
return TO.tensoralloc_contract(
TC,
A, pA, false,
B, pB, false,
pAB, Val(false)
)
M = promote_storagetype(similarstoragetype(A, TC), similarstoragetype(B, TC))
TTC = tensormaptype(S, numout(A), numin(B), M)
structure = codomain(A) ← domain(B)
return TO.tensoralloc(TTC, structure, Val(false))
end

"""
Expand Down Expand Up @@ -292,8 +290,9 @@ function LinearAlgebra.rank(
t::AbstractTensorMap;
atol::Real = 0, rtol::Real = atol > 0 ? 0 : _default_rtol(t)
)
r = 0 * dim(first(allunits(sectortype(t))))
dim(t) == 0 && return r
r = dim(t)
iszero(r) && return r
r = zero(r)
S = MatrixAlgebraKit.svd_vals(t)
tol = max(atol, rtol * maximum(parent(S)))
for (c, b) in pairs(S)
Expand Down Expand Up @@ -325,7 +324,7 @@ end
function LinearAlgebra.tr(t::AbstractTensorMap)
domain(t) == codomain(t) ||
throw(SpaceMismatch("Trace of a tensor only exist when domain == codomain"))
s = zero(scalartype(t))
s = zero(scalartype(t)) * zero(dimscalartype(sectortype(t)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps dimscalartype(I) could have been included in computing scalartype? I don't think there is any case where fusionscalartype would be Int, but dimscalartype would be a floating point type, but I guess adding this here doesn't hurt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about that, I do really think about scalartype as returning the type of the data entries, (even if that does not sound right, this is definitely how we have been using it), so in principle I could conceive of having integer entries, but anyonic symmetry (although I don't know if there really is that much use for that)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only way to have a unitary fusion category with integer F symbol is for Rep{G} or Vec{G} where G is an abelian group. Hence, in all other cases scalartype is at least a non-integer real type. But I am fine leaving this as is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to double check, do you happen to mean sectorscalartype? Otherwise I'm slighty confused

for (c, b) in blocks(t)
s += dim(c) * tr(b)
end
Expand Down Expand Up @@ -538,8 +537,7 @@ absorb(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) = absorb!(copy(tdst), t
function absorb!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap)
numin(tdst) == numin(tsrc) && numout(tdst) == numout(tsrc) ||
throw(DimensionError("Incompatible number of indices for source and destination"))
S = spacetype(tdst)
S == spacetype(tsrc) || throw(SpaceMismatch("incompatible spacetypes"))
S = check_spacetype(tdst, tsrc)
dom = mapreduce(infimum, ⊗, domain(tdst), domain(tsrc); init = one(S))
cod = mapreduce(infimum, ⊗, codomain(tdst), codomain(tsrc); init = one(S))
for (f1, f2) in fusiontrees(cod ← dom)
Expand All @@ -561,7 +559,7 @@ new `TensorMap` instance whose codomain is `codomain(t1) ⊗ codomain(t2)` and w
is `domain(t1) ⊗ domain(t2)`.
"""
function ⊗(A::AbstractTensorMap, B::AbstractTensorMap)
(S = spacetype(A)) === spacetype(B) || throw(SpaceMismatch("incompatible space types"))
check_spacetype(A, B)

# allocate destination with correct scalartype
pA = ((codomainind(A)..., domainind(A)...), ())
Expand Down
5 changes: 5 additions & 0 deletions src/tensors/tensor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ TensorMapWithStorage{T, A}(::UndefInitializer, codomain::TensorSpace, domain::Te
TensorMapWithStorage{T, A}(undef, codomain domain)
TensorWithStorage{T, A}(::UndefInitializer, V::TensorSpace) where {T, A} = TensorMapWithStorage{T, A}(undef, V one(V))

# Utility constructors
# --------------------
TensorMap(t::TensorMap) = copy(t)


# raw data constructors
# ---------------------
# - dispatch starts with TensorMap{T}(::DenseVector{T}, ...)
Expand Down
Loading
Loading