From 5111b2e6d1621f4645da395adf094cf77252ad64 Mon Sep 17 00:00:00 2001 From: Jutho Date: Mon, 24 Jul 2023 17:21:20 +0200 Subject: [PATCH 01/57] update CI, add logo --- .JuliaFormatter.toml | 1 + .github/workflows/ci-julia-nightly.yml | 5 +- .github/workflows/ci.yml | 5 +- .github/workflows/docs.yml | 2 +- .github/workflows/format_check.yml | 42 ++++++ README.md | 28 ++-- docs/src/assets/logo.svg | 181 +++++++++++++++++++++++++ 7 files changed, 249 insertions(+), 15 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 .github/workflows/format_check.yml create mode 100644 docs/src/assets/logo.svg diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 00000000..9613e054 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1 @@ +style = "yas" \ No newline at end of file diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml index 2fd1b019..fe980d3d 100644 --- a/.github/workflows/ci-julia-nightly.yml +++ b/.github/workflows/ci-julia-nightly.yml @@ -18,16 +18,17 @@ jobs: arch: - x64 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest # env: # JULIA_NUM_THREADS: 2 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: file: lcov.info diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81a23014..afa09d8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,16 +19,17 @@ jobs: arch: - x64 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest # env: # JULIA_NUM_THREADS: 2 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: file: lcov.info diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f627c1a9..7f0b3f3e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@latest with: version: '1' diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml new file mode 100644 index 00000000..eb7d11d6 --- /dev/null +++ b/.github/workflows/format_check.yml @@ -0,0 +1,42 @@ +name: format-check + +on: + push: + branches: + - 'master' + - 'release-' + tags: '*' + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: [1] + julia-arch: [x86] + os: [ubuntu-latest] + steps: + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia-version }} + + - uses: actions/checkout@v1 + - name: Install JuliaFormatter and format + # This will use the latest version by default but you can set the version like so: + # + # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' + run: | + julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))' + julia -e 'using JuliaFormatter; format(".", verbose=true)' + - name: Format check + run: | + julia -e ' + out = Cmd(`git diff --name-only`) |> read |> String + if out == "" + exit(0) + else + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) + end' \ No newline at end of file diff --git a/README.md b/README.md index 41202519..0d20d50a 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,34 @@ + + # TensorKit.jl A Julia package for large-scale tensor computations, with a hint of category theory. -| **Documentation** | **Build Status** | -|:-------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------:| -| [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![CI][ci-img]][ci-url] [![CI (Julia nightly)][ci-julia-nightly-img]][ci-julia-nightly-url] [![][codecov-img]][codecov-url] | - -[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg -[docs-dev-url]: https://jutho.github.io/TensorKit.jl/latest +| **Build Status** | **Coverage** | **Quality assurance** | **Downloads** | +|:----------------:|:------------:|:---------------------:|:--------------| +| [![CI][ci-img]][ci-url] [![CI (Julia nightly)][ci-julia-nightly-img]][ci-julia-nightly-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [![Strided Downloads][genie-img]][genie-url] | -[docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg -[docs-stable-url]: https://jutho.github.io/TensorKit.jl/stable +[github-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI/badge.svg +[github-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3ACI [ci-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI/badge.svg [ci-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3ACI -[ci-julia-nightly-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI%20(Julia%20nightly)/badge.svg -[ci-julia-nightly-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3A%22CI+%28Julia+nightly%29%22 +[ci-julia-nightly-img]: + https://github.com/Jutho/TensorKit.jl/workflows/CI%20(Julia%20nightly)/badge.svg +[ci-julia-nightly-url]: + https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3A%22CI+%28Julia+nightly%29%22 [codecov-img]: https://codecov.io/gh/Jutho/TensorKit.jl/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/Jutho/TensorKit.jl +[aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg +[aqua-url]: https://github.com/JuliaTesting/Aqua.jl + +[genie-img]: + https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/TensorKit +[genie-url]: https://pkgs.genieframework.com?packages=TensorKit + Install via the package manager. Check out the [tutorial](https://jutho.github.io/TensorKit.jl/stable/man/tutorial/) and the full [documentation](https://jutho.github.io/TensorKit.jl/stable). diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg new file mode 100644 index 00000000..75413d4c --- /dev/null +++ b/docs/src/assets/logo.svg @@ -0,0 +1,181 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0cc5c795ea75375b2106ccc8153cd62314a62616 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 29 Apr 2023 21:52:08 +0200 Subject: [PATCH 02/57] update Project.toml format --- Project.toml | 18 ++++++++++++++++-- test/Project.toml | 11 ----------- 2 files changed, 16 insertions(+), 13 deletions(-) delete mode 100644 test/Project.toml diff --git a/Project.toml b/Project.toml index 656e36a7..dd3a21ab 100644 --- a/Project.toml +++ b/Project.toml @@ -12,11 +12,25 @@ WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" +[extras] +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SUNRepresentations = "1a50b95c-7aac-476d-a9ce-2bfc675fc617" +TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" +WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" + +[targets] +test = ["Combinatorics", "HalfIntegers", "LinearAlgebra", "Random", "SUNRepresentations", "TensorKit", "TensorOperations", "Test", "TestExtras", "WignerSymbols"] + [compat] Strided = "1.0.1" TupleTools = "1.1" HalfIntegers = "1" WignerSymbols = "1,2" -TensorOperations = "3.2.3" +TensorOperations = "4" LRUCache = "1.0.2" -julia = "1.6" +julia = "1.6 - 1" diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index c4b03936..00000000 --- a/test/Project.toml +++ /dev/null @@ -1,11 +0,0 @@ -[deps] -Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" -HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SUNRepresentations = "1a50b95c-7aac-476d-a9ce-2bfc675fc617" -TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" -TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" -WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" From a57285dfc33703a7f381cf3b39df6afb47958341 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 12 May 2023 21:29:47 +0200 Subject: [PATCH 03/57] remove unsafe_strided --- src/tensors/linalg.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index d5cab859..1184a587 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -275,11 +275,7 @@ function LinearAlgebra.mul!(tC::AbstractTensorMap, A = block(tA, c) B = block(tB, c) C = block(tC, c) - if isbitstype(eltype(A)) && isbitstype(eltype(B)) && isbitstype(eltype(C)) - @unsafe_strided A B C mul!(C, A, B, α, β) - else - mul!(StridedView(C), StridedView(A), StridedView(B), α, β) - end + mul!(StridedView(C), StridedView(A), StridedView(B), α, β) elseif β != one(β) rmul!(block(tC, c), β) end From dbe6d6baf17d009b29c145d21013c91aaf8318f8 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 24 May 2023 16:12:12 +0200 Subject: [PATCH 04/57] Add pentagon and hexagon equation --- src/TensorKit.jl | 5 +- src/sectors/sectors.jl | 112 +++++++++++++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 8320e6b0..620631e2 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -88,7 +88,8 @@ using TupleTools: StaticLength using Strided -using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon +using VectorInterface +using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon, IndexTuple, Index2Tuple, linearize const TO = TensorOperations using LRUCache @@ -111,7 +112,7 @@ using LinearAlgebra: norm, dot, normalize, normalize!, tr, Diagonal, Hermitian import Base.Meta -const IndexTuple{N} = NTuple{N, Int} +# const IndexTuple{N} = NTuple{N, Int} # Auxiliary files #----------------- diff --git a/src/sectors/sectors.jl b/src/sectors/sectors.jl index 8fe04a26..42bb6bdd 100644 --- a/src/sectors/sectors.jl +++ b/src/sectors/sectors.jl @@ -68,9 +68,11 @@ 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) -Base.getindex(::SectorValues{Trivial}, i::Int) = i == 1 ? Trivial() : - throw(BoundsError(values(Trivial), i)) +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)) +end findindex(::SectorValues{Trivial}, c::Trivial) = 1 """ @@ -99,9 +101,10 @@ which case it is complex). function Base.isreal(I::Type{<:Sector}) u = one(I) if BraidingStyle(I) isa HasBraiding - return (eltype(Fsymbol(u, u, u, u, u, u))<:Real) && (eltype(Rsymbol(u, u, u))<:Real) + return (eltype(Fsymbol(u, u, u, u, u, u)) <: Real) && + (eltype(Rsymbol(u, u, u)) <: Real) else - return (eltype(Fsymbol(u, u, u, u, u, u))<:Real) + return (eltype(Fsymbol(u, u, u, u, u, u)) <: Real) end end Base.isreal(::Type{Trivial}) = true @@ -139,7 +142,7 @@ struct SimpleFusion <: MultipleFusion # multiple fusion but multiplicity free end struct GenericFusion <: MultipleFusion # multiple fusion with multiplicities end -const MultiplicityFreeFusion = Union{UniqueFusion, SimpleFusion} +const MultiplicityFreeFusion = Union{UniqueFusion,SimpleFusion} """ FusionStyle(a::Sector) -> ::FusionStyle @@ -223,8 +226,9 @@ fusion output occurs only once and `k == 1`, the default is to suppress vertex l setting them equal to `nothing`. For `FusionStyle(I) == GenericFusion()`, the default is to just use `k`, unless a specialized method is provided. """ -vertex_ind2label(k::Int, a::I, b::I, c::I) where {I<:Sector}= - _ind2label(FusionStyle(I), k::Int, a::I, b::I, c::I) +function vertex_ind2label(k::Int, a::I, b::I, c::I) where {I<:Sector} + return _ind2label(FusionStyle(I), k::Int, a::I, b::I, c::I) +end _ind2label(::UniqueFusion, k, a, b, c) = nothing _ind2label(::SimpleFusion, k, a, b, c) = nothing _ind2label(::GenericFusion, k, a, b, c) = k @@ -255,9 +259,9 @@ function dim(a::Sector) if FusionStyle(a) isa UniqueFusion 1 elseif FusionStyle(a) isa SimpleFusion - abs(1/Fsymbol(a, conj(a), a, a, one(a), one(a))) + abs(1 / Fsymbol(a, conj(a), a, a, one(a), one(a))) else - abs(1/Fsymbol(a, conj(a), a, a, one(a), one(a))[1]) + abs(1 / Fsymbol(a, conj(a), a, a, one(a), one(a))[1]) end end sqrtdim(a::Sector) = (FusionStyle(a) isa UniqueFusion) ? 1 : sqrt(dim(a)) @@ -281,7 +285,7 @@ end Return the twist of a sector `a` """ -twist(a::Sector) = sum(dim(b)/dim(a)*tr(Rsymbol(a,a,b)) for b in a ⊗ a) +twist(a::Sector) = sum(dim(b) / dim(a) * tr(Rsymbol(a, a, b)) for b in a ⊗ a) """ Bsymbol(a::I, b::I, c::I) where {I<:Sector} @@ -299,21 +303,22 @@ number. Otherwise it is a square matrix with row and column size """ function Bsymbol(a::I, b::I, c::I) where {I<:Sector} if FusionStyle(I) isa UniqueFusion || FusionStyle(I) isa SimpleFusion - (sqrtdim(a)*sqrtdim(b)*isqrtdim(c))*Fsymbol(a, b, dual(b), a, c, one(a)) + (sqrtdim(a) * sqrtdim(b) * isqrtdim(c)) * Fsymbol(a, b, dual(b), a, c, one(a)) else - reshape((sqrtdim(a)*sqrtdim(b)*isqrtdim(c))*Fsymbol(a, b, dual(b), a, c, one(a)), - (Nsymbol(a, b, c), Nsymbol(c, dual(b), a))) + reshape((sqrtdim(a) * sqrtdim(b) * isqrtdim(c)) * + Fsymbol(a, b, dual(b), a, c, one(a)), + (Nsymbol(a, b, c), Nsymbol(c, dual(b), a))) end end # Not necessary function Asymbol(a::I, b::I, c::I) where {I<:Sector} if FusionStyle(I) isa UniqueFusion || FusionStyle(I) isa SimpleFusion - (sqrtdim(a)*sqrtdim(b)*isqrtdim(c))* - conj(frobeniusschur(a)*Fsymbol(dual(a), a, b, b, one(a), c)) + (sqrtdim(a) * sqrtdim(b) * isqrtdim(c)) * + conj(frobeniusschur(a) * Fsymbol(dual(a), a, b, b, one(a), c)) else - reshape((sqrtdim(a)*sqrtdim(b)*isqrtdim(c))* - conj(frobeniusschur(a)*Fsymbol(dual(a), a, b, b, one(a), c)), + reshape((sqrtdim(a) * sqrtdim(b) * isqrtdim(c)) * + conj(frobeniusschur(a) * Fsymbol(dual(a), a, b, b, one(a), c)), (Nsymbol(a, b, c), Nsymbol(dual(a), c, b))) end end @@ -356,19 +361,82 @@ braids are in fact equivalent to crossings (i.e. braiding twice is an identity: BraidingStyle(a::Sector) = BraidingStyle(typeof(a)) BraidingStyle(::Type{Trivial}) = Bosonic() +# Pentagon and Hexagon equations +#------------------------------------------------------------------------------- +# Consistency equations for F- and R-symbols +function pentagon_equation(a::I, b::I, c::I, d::I; kwargs...) where {I<:Sector} + for f in ⊗(a, b), h in ⊗(c, d) + for g in ⊗(f, c), i in ⊗(b, h) + for e in intersect(⊗(g, d), ⊗(a, i)) + if FusionStyle(I) isa MultiplicityFreeFusion + p1 = Fsymbol(f, c, d, e, g, h) * Fsymbol(a, b, h, e, f, i) + p2 = zero(p1) + for j in ⊗(b, c) + p2 += Fsymbol(a, b, c, g, f, j) * + Fsymbol(a, j, d, e, g, i) * + Fsymbol(b, c, d, i, j, h) + end + else + @tensor p1[λ, μ, ν, κ, ρ, σ] := Fsymbol(f, c, d, e, g, h)[λ, μ, ν, τ] * + Fsymbol(a, b, h, e, f, i)[κ, τ, ρ, σ] + p2 = zero(p1) + for j in ⊗(b, c) + @tensor p2[λ, μ, ν, κ, ρ, σ] += Fsymbol(a, b, c, g, f, j)[κ, λ, α, + β] * + Fsymbol(a, j, d, e, g, i)[β, μ, τ, + σ] * + Fsymbol(b, c, d, i, j, h)[α, τ, ν, + ρ] + end + end + isapprox(p1, p2; kwargs...) || return false + end + end + end + return true +end + +function hexagon_equation(a::I, b::I, c::I; kwargs...) where {I<:Sector} + for e in ⊗(c, a), f in ⊗(c, b) + for d in intersect(⊗(e, b), ⊗(a, f)) + if FusionStyle(I) isa MultiplicityFreeFusion + p1 = Rsymbol(a, c, e) * Fsymbol(a, c, b, d, e, f) * Rsymbol(b, c, f) + p2 = zero(p1) + for g in ⊗(a, b) + p2 += Fsymbol(c, a, b, d, e, g) * Rsymbol(g, c, d) * + Fsymbol(a, b, c, d, g, f) + end + else + @tensor p1[α, β, μ, ν] := Rsymbol(a, c, e)[α, λ] * + Fsymbol(a, c, b, d, e, f)[λ, β, γ, ν] * + Rsymbol(b, c, f)[γ, μ] + p2 = zero(p1) + for g in ⊗(a, b) + @tensor p2[α, β, μ, ν] += Fsymbol(c, a, b, d, e, g)[α, β, δ, + σ] * + Rsymbol(g, c, d)[σ, ψ] * + Fsymbol(a, b, c, d, g, f)[δ, ψ, μ, ν] + end + end + isapprox(p1, p2; kwargs...) || return false + end + end + return true +end + # SectorSet: #------------------------------------------------------------------------------- # Custum generator to represent sets of sectors with type inference -struct SectorSet{I<:Sector, F, S} +struct SectorSet{I<:Sector,F,S} f::F set::S end -SectorSet{I}(::Type{F}, set::S) where {I<:Sector, F, S} = SectorSet{I, Type{F}, S}(F, set) -SectorSet{I}(f::F, set::S) where {I<:Sector, F, S} = SectorSet{I, F, S}(f, set) +SectorSet{I}(::Type{F}, set::S) where {I<:Sector,F,S} = SectorSet{I,Type{F},S}(F, set) +SectorSet{I}(f::F, set::S) where {I<:Sector,F,S} = SectorSet{I,F,S}(f, set) SectorSet{I}(set) where {I<:Sector} = SectorSet{I}(identity, set) Base.IteratorEltype(::Type{<:SectorSet}) = HasEltype() -Base.IteratorSize(::Type{SectorSet{I, F, S}}) where {I<:Sector, F, S} = Base.IteratorSize(S) +Base.IteratorSize(::Type{SectorSet{I,F,S}}) where {I<:Sector,F,S} = Base.IteratorSize(S) Base.eltype(::SectorSet{I}) where {I<:Sector} = I Base.length(s::SectorSet) = length(s.set) From 2821c9ec71043fd2253291595a938219fced7d4c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 24 May 2023 16:13:58 +0200 Subject: [PATCH 05/57] Start switch to TensorOperations v4 --- Project.toml | 27 +- src/fusiontrees/fusiontrees.jl | 8 +- src/tensors/abstracttensor.jl | 6 +- src/tensors/tensoroperations.jl | 430 ++++++++++++++++++-------------- test/fusiontrees.jl | 47 +--- test/runtests.jl | 3 +- test/sectors.jl | 94 ++----- 7 files changed, 301 insertions(+), 314 deletions(-) diff --git a/Project.toml b/Project.toml index dd3a21ab..45b2baa2 100644 --- a/Project.toml +++ b/Project.toml @@ -4,13 +4,23 @@ authors = ["Jutho Haegeman"] version = "0.10.1" [deps] +HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" +LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Strided = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" +TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" TupleTools = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6" -HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" +VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" -TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" -LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" + +[compat] +HalfIntegers = "1" +LRUCache = "1.0.2" +Strided = "2" +TensorOperations = "4" +TupleTools = "1.1" +WignerSymbols = "1,2" +julia = "1.6 - 1" [extras] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" @@ -24,13 +34,4 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" [targets] -test = ["Combinatorics", "HalfIntegers", "LinearAlgebra", "Random", "SUNRepresentations", "TensorKit", "TensorOperations", "Test", "TestExtras", "WignerSymbols"] - -[compat] -Strided = "1.0.1" -TupleTools = "1.1" -HalfIntegers = "1" -WignerSymbols = "1,2" -TensorOperations = "4" -LRUCache = "1.0.2" -julia = "1.6 - 1" +test = ["Combinatorics", "HalfIntegers", "LinearAlgebra", "Random", "SUNRepresentations", "TensorOperations", "Test", "TestExtras", "WignerSymbols"] diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index 92ad6124..052e5fd9 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -174,13 +174,13 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 2}) where {I} Xtemp = X X = similar(Xtemp) Za = convert(A, FusionTree((a,), a, (isduala,), ())) - TO.contract!(1, Za, :N, Xtemp, :N, 0, X, (1,), (2,), (2, 3), (1,), (1,2,3)) + TO.tensorcontract!(X, ((1,2,3), ()), Za, ((1,), (2,)), :N, Xtemp, ((1,), (2,3)), :N, true, false) end if isdualb Xtemp = X X = similar(Xtemp) Zb = convert(A, FusionTree((b,), b, (isdualb,), ())) - TO.contract!(1, Zb, :N, Xtemp, :N, 0, X, (1,), (2,), (1, 3), (2,), (2,1,3)) + TO.tensorcontract!(X, ((2, 1, 3), ()), Zb, ((1,), (2,)), :N, Xtemp, ((2,), (1, 3)), :N, true, false) end return X end @@ -198,9 +198,7 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,N}) where {I,N} d1 = size(C1) X = similar(C1, (d1[1], d1[2], Base.tail(dtail)...)) trivialtuple = ntuple(identity, Val(N)) - TO.contract!(1, C1, :N, Ctail, :N, 0, X, - (1,2), (3,), Base.tail(trivialtuple), (1,), (trivialtuple..., N+1)) - return X + return TO.tensorcontract!(X, ((trivialtuple..., N+1), ()), C1, ((1,2), (3,)), :N, Ctail, ((1,), Base.tail(trivialtuple)), :N, true, false) end # Show methods diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 1a4abdf1..a7377773 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -75,7 +75,7 @@ codomainind(t::AbstractTensorMap) = codomainind(typeof(t)) domainind(t::AbstractTensorMap) = domainind(typeof(t)) allind(t::AbstractTensorMap) = allind(typeof(t)) -function adjointtensorindex(t::AbstractTensorMap{<:IndexSpace,N₁,N₂}, i) where {N₁,N₂} +function adjointtensorindex(::AbstractTensorMap{<:IndexSpace,N₁,N₂}, i) where {N₁,N₂} return ifelse(i <= N₁, N₂ + i, i - N₁) end @@ -83,6 +83,10 @@ function adjointtensorindices(t::AbstractTensorMap, indices::IndexTuple) return map(i -> adjointtensorindex(t, i), indices) end +function adjointtensorindices(t::AbstractTensorMap, p::Index2Tuple) + return adjointtensorindices(t, p[1]), adjointtensorindices(t, p[2]) +end + # Equality and approximality #---------------------------- function Base.:(==)(t1::AbstractTensorMap, t2::AbstractTensorMap) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 18c1efe1..6040e0a8 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -1,8 +1,8 @@ function cached_permute(sym::Symbol, t::TensorMap{S}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=(); - copy::Bool = false) where {S, N₁, N₂} - cod = ProductSpace{S, N₁}(map(n->space(t, n), p1)) - dom = ProductSpace{S, N₂}(map(n->dual(space(t, n)), p2)) + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=(); + copy::Bool=false) where {S,N₁,N₂} + cod = ProductSpace{S,N₁}(map(n -> space(t, n), p1)) + dom = ProductSpace{S,N₂}(map(n -> dual(space(t, n)), p2)) # share data if possible if !copy if p1 === codomainind(t) && p2 === domainind(t) @@ -19,20 +19,16 @@ function cached_permute(sym::Symbol, t::TensorMap{S}, end function cached_permute(sym::Symbol, t::AdjointTensorMap, - p1::IndexTuple, p2::IndexTuple=(); - copy::Bool = false) + p1::IndexTuple, p2::IndexTuple=(); + copy::Bool=false) p1′ = adjointtensorindices(t, p2) p2′ = adjointtensorindices(t, p1) - adjoint(cached_permute(sym, adjoint(t), p1′, p2′; copy = copy)) + return adjoint(cached_permute(sym, adjoint(t), p1′, p2′; copy=copy)) end -scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} = - dim(codomain(t)) == dim(domain(t)) == 1 ? - first(blocks(t))[2][1, 1] : throw(SpaceMismatch()) - @propagate_inbounds function add!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S}, - p1::IndexTuple, p2::IndexTuple) where {S} + β, tdst::AbstractTensorMap{S}, + p1::IndexTuple, p2::IndexTuple) where {S} I = sectortype(S) if BraidingStyle(I) isa SymmetricBraiding add_permute!(α, tsrc, β, tdst, p1, p2) @@ -41,48 +37,45 @@ scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} = end end @propagate_inbounds function add!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S}, - p1::IndexTuple, p2::IndexTuple, - levels::IndexTuple) where {S} - add_braid!(α, tsrc, β, tdst, p1, p2, levels) + β, tdst::AbstractTensorMap{S}, + p1::IndexTuple, p2::IndexTuple, + levels::IndexTuple) where {S} + return add_braid!(α, tsrc, β, tdst, p1, p2, levels) end @propagate_inbounds function add_permute!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S, N₁, N₂}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}) where {S, N₁, N₂} - - _add!(α, tsrc, β, tdst, p1, p2, (f1, f2)->permute(f1, f2, p1, p2)) + β, tdst::AbstractTensorMap{S,N₁,N₂}, + p1::IndexTuple{N₁}, + p2::IndexTuple{N₂}) where {S,N₁,N₂} + return _add!(α, tsrc, β, tdst, p1, p2, (f1, f2) -> permute(f1, f2, p1, p2)) end @propagate_inbounds function add_braid!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S, N₁, N₂}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}, - levels::IndexTuple) where {S, N₁, N₂} - + β, tdst::AbstractTensorMap{S,N₁,N₂}, + p1::IndexTuple{N₁}, + p2::IndexTuple{N₂}, + levels::IndexTuple) where {S,N₁,N₂} length(levels) == numind(tsrc) || throw(ArgumentError("incorrect levels $levels for tensor map $(codomain(tsrc)) ← $(domain(tsrc))")) levels1 = TupleTools.getindices(levels, codomainind(tsrc)) levels2 = TupleTools.getindices(levels, domainind(tsrc)) - _add!(α, tsrc, β, tdst, p1, p2, (f1, f2)->braid(f1, f2, levels1, levels2, p1, p2)) + return _add!(α, tsrc, β, tdst, p1, p2, + (f1, f2) -> braid(f1, f2, levels1, levels2, p1, p2)) end @propagate_inbounds function add_transpose!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S, N₁, N₂}, + β, tdst::AbstractTensorMap{S,N₁,N₂}, p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}) where {S, N₁, N₂} - - _add!(α, tsrc, β, tdst, p1, p2, (f1, f2)->transpose(f1, f2, p1, p2)) + p2::IndexTuple{N₂}) where {S,N₁,N₂} + return _add!(α, tsrc, β, tdst, p1, p2, (f1, f2) -> transpose(f1, f2, p1, p2)) end - -function _add!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S, N₁, N₂}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, fusiontreemap) where {S, N₁, N₂} +function _add!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, fusiontreemap) where {S,N₁,N₂} @boundscheck begin - all(i->space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || + all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || throw(SpaceMismatch("tsrc = $(codomain(tsrc))←$(domain(tsrc)), tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i->space(tsrc, p2[i]) == space(tdst, N₁+i), 1:N₂) || + all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || throw(SpaceMismatch("tsrc = $(codomain(tsrc))←$(domain(tsrc)), tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) end @@ -101,7 +94,7 @@ function _add!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S, N end function _add_trivial_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) + p1::IndexTuple, p2::IndexTuple, fusiontreemap) cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) @@ -111,12 +104,13 @@ function _add_trivial_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTen end function _add_abelian_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) + p1::IndexTuple, p2::IndexTuple, fusiontreemap) if Threads.nthreads() > 1 nstridedthreads = Strided.get_num_threads() Strided.set_num_threads(1) Threads.@sync for (f1, f2) in fusiontrees(tsrc) - Threads.@spawn _addabelianblock!(α, tsrc, β, tdst, p1, p2, f1, f2, fusiontreemap) + Threads.@spawn _addabelianblock!(α, tsrc, β, tdst, p1, p2, f1, f2, + fusiontreemap) end Strided.set_num_threads(nstridedthreads) else # debugging is easier this way @@ -128,19 +122,19 @@ function _add_abelian_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTen end function _addabelianblock!(α, tsrc::AbstractTensorMap, - β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, - f1::FusionTree, f2::FusionTree, - fusiontreemap) + β, tdst::AbstractTensorMap, + p1::IndexTuple, p2::IndexTuple, + f1::FusionTree, f2::FusionTree, + fusiontreemap) cod = codomain(tsrc) dom = domain(tsrc) (f1′, f2′), coeff = first(fusiontreemap(f1, f2)) pdata = (p1..., p2...) - @inbounds axpby!(α*coeff, permutedims(tsrc[f1, f2], pdata), β, tdst[f1′, f2′]) + @inbounds axpby!(α * coeff, permutedims(tsrc[f1, f2], pdata), β, tdst[f1′, f2′]) end function _add_general_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) + p1::IndexTuple, p2::IndexTuple, fusiontreemap) cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) @@ -152,7 +146,7 @@ function _add_general_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTen end for (f1, f2) in fusiontrees(tsrc) for ((f1′, f2′), coeff) in fusiontreemap(f1, f2) - @inbounds axpy!(α*coeff, permutedims(tsrc[f1, f2], pdata), tdst[f1′, f2′]) + @inbounds axpy!(α * coeff, permutedims(tsrc[f1, f2], pdata), tdst[f1′, f2′]) end end return nothing @@ -160,21 +154,20 @@ end const _add_kernels = (_add_trivial_kernel!, _add_abelian_kernel!, _add_general_kernel!) -function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S, N₁, N₂}, +function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S, N₁, N₂, N₃} - + q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S,N₁,N₂,N₃} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @boundscheck begin - all(i->space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || + all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i->space(tsrc, p2[i]) == space(tdst, N₁+i), 1:N₂) || + all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i->space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || + all(i -> space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), q1 = $(q1), q2 = $(q2)")) end @@ -186,8 +179,8 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S, N n = length(cod) pdata = (p1..., p2...) TO._trace!(α, tsrc[], β, tdst[], pdata, q1, q2) - # elseif FusionStyle(I) isa UniqueFusion - # TODO: is it worth multithreading UniqueFusion case for traces? + # elseif FusionStyle(I) isa UniqueFusion + # TODO: is it worth multithreading UniqueFusion case for traces? else cod = codomain(tsrc) dom = domain(tsrc) @@ -205,13 +198,14 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S, N f1′′, g1 = split(f1′, N₁) f2′′, g2 = split(f2′, N₂) if g1 == g2 - coeff *= dim(g1.coupled)/dim(g1.uncoupled[1]) - for i = 2:length(g1.uncoupled) + coeff *= dim(g1.coupled) / dim(g1.uncoupled[1]) + for i in 2:length(g1.uncoupled) if !(g1.isdual[i]) coeff *= twist(g1.uncoupled[i]) end end - TO._trace!(α*coeff, tsrc[f1, f2], true, tdst[f1′′, f2′′], pdata, q1, q2) + TO._trace!(α * coeff, tsrc[f1, f2], true, tdst[f1′′, f2′′], pdata, q1, + q2) end end end @@ -223,16 +217,16 @@ end # permute the fusion tree and should therefore be special cased. This will speed # up MPS algorithms function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple{N₁}, cindA::IndexTuple, - oindB::IndexTuple{N₂}, cindB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}} = nothing) where {S, N₁, N₂} + β, C::AbstractTensorMap{S}, + oindA::IndexTuple{N₁}, cindA::IndexTuple, + oindB::IndexTuple{N₂}, cindB::IndexTuple, + p1::IndexTuple, p2::IndexTuple, + syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S,N₁,N₂} # find optimal contraction scheme hsp = has_shared_permute ipC = TupleTools.invperm((p1..., p2...)) - oindAinC = TupleTools.getindices(ipC, ntuple(n->n, N₁)) - oindBinC = TupleTools.getindices(ipC, ntuple(n->n+N₁, N₂)) + oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) + oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) qA = TupleTools.sortperm(cindA) cindA′ = TupleTools.getindices(cindA, qA) @@ -245,18 +239,18 @@ function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, dA, dB, dC = dim(A), dim(B), dim(C) # keep order A en B, check possibilities for cind - memcost1 = memcost2 = dC*(!hsp(C, oindAinC, oindBinC)) - memcost1 += dA*(!hsp(A, oindA, cindA′)) + - dB*(!hsp(B, cindB′, oindB)) - memcost2 += dA*(!hsp(A, oindA, cindA′′)) + - dB*(!hsp(B, cindB′′, oindB)) + memcost1 = memcost2 = dC * (!hsp(C, oindAinC, oindBinC)) + memcost1 += dA * (!hsp(A, oindA, cindA′)) + + dB * (!hsp(B, cindB′, oindB)) + memcost2 += dA * (!hsp(A, oindA, cindA′′)) + + dB * (!hsp(B, cindB′′, oindB)) # reverse order A en B, check possibilities for cind - memcost3 = memcost4 = dC*(!hsp(C, oindBinC, oindAinC)) - memcost3 += dB*(!hsp(B, oindB, cindB′)) + - dA*(!hsp(A, cindA′, oindA)) - memcost4 += dB*(!hsp(B, oindB, cindB′′)) + - dA*(!hsp(A, cindA′′, oindA)) + memcost3 = memcost4 = dC * (!hsp(C, oindBinC, oindAinC)) + memcost3 += dB * (!hsp(B, oindB, cindB′)) + + dA * (!hsp(A, cindA′, oindA)) + memcost4 += dB * (!hsp(B, oindB, cindB′′)) + + dA * (!hsp(A, cindA′′, oindA)) if min(memcost1, memcost2) <= min(memcost3, memcost4) if memcost1 <= memcost2 @@ -265,8 +259,8 @@ function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p1, p2, syms) end else - p1′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p1) - p2′ = map(n->ifelse(n>N₁, n-N₁, n+N₂), p2) + p1′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p1) + p2′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p2) if memcost3 <= memcost4 return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′, syms) else @@ -280,8 +274,7 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, oindA::IndexTuple{N₁}, cindA::IndexTuple, oindB::IndexTuple{N₂}, cindB::IndexTuple, p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}} = nothing) where {S, N₁, N₂} - + syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S,N₁,N₂} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @@ -294,10 +287,10 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, end end if syms === nothing - A′ = permute(A, oindA, cindA; copy = copyA) + A′ = permute(A, oindA, cindA; copy=copyA) B′ = permute(B, cindB, oindB) else - A′ = cached_permute(syms[1], A, oindA, cindA; copy = copyA) + A′ = cached_permute(syms[1], A, oindA, cindA; copy=copyA) B′ = cached_permute(syms[2], B, cindB, oindB) end if BraidingStyle(sectortype(S)) isa Fermionic @@ -308,19 +301,20 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, end end ipC = TupleTools.invperm((p1..., p2...)) - oindAinC = TupleTools.getindices(ipC, ntuple(n->n, N₁)) - oindBinC = TupleTools.getindices(ipC, ntuple(n->n+N₁, N₂)) + oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) + oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) if has_shared_permute(C, oindAinC, oindBinC) C′ = permute(C, oindAinC, oindBinC) mul!(C′, A′, B′, α, β) else if syms === nothing - C′ = A′*B′ + C′ = A′ * B′ else p1′ = ntuple(identity, N₁) p2′ = N₁ .+ ntuple(identity, N₂) TC = eltype(C) - C′ = TO.cached_similar_from_indices(syms[3], TC, oindA, oindB, p1′, p2′, A, B, :N, :N) + C′ = TO.cached_similar_from_indices(syms[3], TC, oindA, oindB, p1′, p2′, A, B, + :N, :N) mul!(C′, A′, B′) end add!(α, C′, β, C, p1, p2) @@ -328,77 +322,23 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, return C end -# Add support for cache and API (`@tensor` macro & friends) from TensorOperations.jl: -# compatibility layer -function TensorOperations.memsize(t::TensorMap) - s = 0 - for (c, b) in blocks(t) - s += sizeof(b) - end - return s -end -TensorOperations.memsize(t::AdjointTensorMap) = TensorOperations.memsize(t') - -function TO.similarstructure_from_indices(T::Type, p1::IndexTuple, p2::IndexTuple, - A::AbstractTensorMap, CA::Symbol = :N) - if CA == :N - _similarstructure_from_indices(T, p1, p2, A) - else - p1 = adjointtensorindices(A, p1) - p2 = adjointtensorindices(A, p2) - _similarstructure_from_indices(T, p1, p2, adjoint(A)) - end -end - -function TO.similarstructure_from_indices(T::Type, poA::IndexTuple, poB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, - A::AbstractTensorMap, B::AbstractTensorMap, - CA::Symbol = :N, CB::Symbol = :N) - - if CA == :N && CB == :N - _similarstructure_from_indices(T, poA, poB, p1, p2, A, B) - elseif CA == :C && CB == :N - poA = adjointtensorindices(A, poA) - _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), B) - elseif CA == :N && CB == :C - poB = adjointtensorindices(B, poB) - _similarstructure_from_indices(T, poA, poB, p1, p2, A, adjoint(B)) - else - poA = adjointtensorindices(A, poA) - poB = adjointtensorindices(B, poB) - _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), adjoint(B)) - end -end - -function _similarstructure_from_indices(::Type{T}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - t::AbstractTensorMap{S}) where {T, S<:IndexSpace, N₁, N₂} - - cod = ProductSpace{S, N₁}(space.(Ref(t), p1)) - dom = ProductSpace{S, N₂}(dual.(space.(Ref(t), p2))) - return dom→cod +function scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} + return dim(codomain(t)) == dim(domain(t)) == 1 ? + first(blocks(t))[2][1, 1] : throw(SpaceMismatch()) end -function _similarstructure_from_indices(::Type{T}, oindA::IndexTuple, oindB::IndexTuple, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - tA::AbstractTensorMap{S}, tB::AbstractTensorMap{S}) where {T, S<:IndexSpace, N₁, N₂} - - spaces = (space.(Ref(tA), oindA)..., space.(Ref(tB), oindB)...) - cod = ProductSpace{S, N₁}(getindex.(Ref(spaces), p1)) - dom = ProductSpace{S, N₂}(dual.(getindex.(Ref(spaces), p2))) - return dom→cod -end - -TO.scalar(t::AbstractTensorMap) = scalar(t) -function TO.add!(α, tsrc::AbstractTensorMap{S}, CA::Symbol, β, - tdst::AbstractTensorMap{S, N₁, N₂}, p1::IndexTuple, p2::IndexTuple) where {S, N₁, N₂} +TO.tensorscalar(t::AbstractTensorMap) = scalar(t) - if CA == :N - p = (p1..., p2...) +function TO.tensoradd!(tdst::AbstractTensorMap{S}{S}, + tsrc::AbstractTensorMap{S}, pA::Index2Tuple, + conjA::Symbol, α::Number, β::Number) where {S} + if conjA == :N + p = linearize(pA) pl = TupleTools.getindices(p, codomainind(tdst)) pr = TupleTools.getindices(p, domainind(tdst)) add!(α, tsrc, β, tdst, pl, pr) else - p = adjointtensorindices(tsrc, (p1..., p2...)) + p = adjointtensorindices(tsrc, linearize(pA)) pl = TupleTools.getindices(p, codomainind(tdst)) pr = TupleTools.getindices(p, domainind(tdst)) add!(α, adjoint(tsrc), β, tdst, pl, pr) @@ -406,56 +346,168 @@ function TO.add!(α, tsrc::AbstractTensorMap{S}, CA::Symbol, β, return tdst end -function TO.trace!(α, tsrc::AbstractTensorMap{S}, CA::Symbol, β, - tdst::AbstractTensorMap{S, N₁, N₂}, p1::IndexTuple, p2::IndexTuple, - q1::IndexTuple, q2::IndexTuple) where {S, N₁, N₂} - - if CA == :N - p = (p1..., p2...) +function TO.tensortrace!(tdst::AbstractTensorMap{S}, + pC::Index2Tuple, tsrc::AbstractTensorMap{S}, + pA::Index2Tuple, conjA::Symbol, α::Number, + β::Number) where {S} + if conjA == :N + p = linearize(pC) pl = TupleTools.getindices(p, codomainind(tdst)) pr = TupleTools.getindices(p, domainind(tdst)) - trace!(α, tsrc, β, tdst, pl, pr, q1, q2) + trace!(α, tsrc, β, tdst, pl, pr, pA[1], pA[2]) else - p = adjointtensorindices(tsrc, (p1..., p2...)) + p = adjointtensorindices(tsrc, linearize(pC)) pl = TupleTools.getindices(p, codomainind(tdst)) pr = TupleTools.getindices(p, domainind(tdst)) - q1 = adjointtensorindices(tsrc, q1) - q2 = adjointtensorindices(tsrc, q2) + q1 = adjointtensorindices(tsrc, pA[1]) + q2 = adjointtensorindices(tsrc, pA[2]) trace!(α, adjoint(tsrc), β, tdst, pl, pr, q1, q2) end return tdst end -function TO.contract!(α, - tA::AbstractTensorMap{S}, CA::Symbol, - tB::AbstractTensorMap{S}, CB::Symbol, - β, tC::AbstractTensorMap{S, N₁, N₂}, - oindA::IndexTuple, cindA::IndexTuple, - oindB::IndexTuple, cindB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}} = nothing) where {S, N₁, N₂} - - p = (p1..., p2...) - pl = ntuple(n->p[n], N₁) - pr = ntuple(n->p[N₁+n], N₂) - if CA == :N && CB == :N - contract!(α, tA, tB, β, tC, oindA, cindA, oindB, cindB, pl, pr, syms) - elseif CA == :N && CB == :C - oindB = adjointtensorindices(tB, oindB) - cindB = adjointtensorindices(tB, cindB) - contract!(α, tA, tB', β, tC, oindA, cindA, oindB, cindB, pl, pr, syms) - elseif CA == :C && CB == :N - oindA = adjointtensorindices(tA, oindA) - cindA = adjointtensorindices(tA, cindA) - contract!(α, tA', tB, β, tC, oindA, cindA, oindB, cindB, pl, pr, syms) - elseif CA == :C && CB == :C - oindA = adjointtensorindices(tA, oindA) - cindA = adjointtensorindices(tA, cindA) - oindB = adjointtensorindices(tB, oindB) - cindB = adjointtensorindices(tB, cindB) - contract!(α, tA', tB', β, tC, oindA, cindA, oindB, cindB, pl, pr, syms) +# # function TO.similarstructure_from_indices(T::Type, p1::IndexTuple, p2::IndexTuple, +# # A::AbstractTensorMap, CA::Symbol=:N) +# # if CA == :N +# # _similarstructure_from_indices(T, p1, p2, A) +# # else +# # p1 = adjointtensorindices(A, p1) +# # p2 = adjointtensorindices(A, p2) +# # _similarstructure_from_indices(T, p1, p2, adjoint(A)) +# # end +# # end + +# # function TO.similarstructure_from_indices(T::Type, poA::IndexTuple, poB::IndexTuple, +# # p1::IndexTuple, p2::IndexTuple, +# # A::AbstractTensorMap, B::AbstractTensorMap, +# # CA::Symbol=:N, CB::Symbol=:N) +# # if CA == :N && CB == :N +# # _similarstructure_from_indices(T, poA, poB, p1, p2, A, B) +# # elseif CA == :C && CB == :N +# # poA = adjointtensorindices(A, poA) +# # _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), B) +# # elseif CA == :N && CB == :C +# # poB = adjointtensorindices(B, poB) +# # _similarstructure_from_indices(T, poA, poB, p1, p2, A, adjoint(B)) +# # else +# # poA = adjointtensorindices(A, poA) +# # poB = adjointtensorindices(B, poB) +# # _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), adjoint(B)) +# # end +# # end + +# function _similarstructure_from_indices(::Type{T}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, +# t::AbstractTensorMap{S}) where {T,S<:IndexSpace,N₁, +# N₂} +# cod = ProductSpace{S,N₁}(space.(Ref(t), p1)) +# dom = ProductSpace{S,N₂}(dual.(space.(Ref(t), p2))) +# return dom → cod +# end +# function _similarstructure_from_indices(::Type{T}, oindA::IndexTuple, oindB::IndexTuple, +# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, +# tA::AbstractTensorMap{S}, +# tB::AbstractTensorMap{S}) where {T,S<:IndexSpace,N₁, +# N₂} +# spaces = (space.(Ref(tA), oindA)..., space.(Ref(tB), oindB)...) +# cod = ProductSpace{S,N₁}(getindex.(Ref(spaces), p1)) +# dom = ProductSpace{S,N₂}(dual.(getindex.(Ref(spaces), p2))) +# return dom → cod +# end + +function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, + pC::Index2Tuple, + A::AbstractTensorMap{S}, pA::Index2Tuple, + conjA::Symbol, + B::AbstractTensorMap{S}, pB::Index2Tuple, + conjB::Symbol, + α::Number, β::Number) where {S,N₁,N₂} + p = linearize(pC) + pl = ntuple(n -> p[n], N₁) + pr = ntuple(n -> p[N₁ + n], N₂) + + if conjA == :C + pA = adjointtensorindices(A, pA) + A = A' + elseif conjA != :N + throw(ArgumentError("unknown conjugation flag $conjA")) + end + + if conjB == :C + pB = adjointtensorindices(B, pB) + B = B' + elseif conjB != :N + throw(ArgumentError("unknown conjugation flag $conjB")) + end + + contract!(α, A, B, β, C, pA[1], pA[2], pB[2], pB[1], pl, pr) + return C + + # if conjA == :N && conjB == :N + # contract!(α, tA, tB, β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) + # elseif conjA == :N && conjB == :C + # pB[2] = adjointtensorindices(tB, pB[2]) + # pB[1] = adjointtensorindices(tB, pB[1]) + # contract!(α, tA, tB', β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) + # elseif conjA == :C && conjB == :N + # pA[1] = adjointtensorindices(tA, pA[1]) + # pA[2] = adjointtensorindices(tA, pA[2]) + # contract!(α, tA', tB, β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) + # elseif conjA == :C && conjB == :C + # pA[1] = adjointtensorindices(tA, pA[1]) + # pA[2] = adjointtensorindices(tA, pA[2]) + # pB[2] = adjointtensorindices(tB, pB[2]) + # pB[1] = adjointtensorindices(tB, pB[1]) + # contract!(α, tA', tB', β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) + # else + # error("unknown conjugation flags: $conjA and $conjB") + # end + # return tC +end + +function TO.tensoradd_type(TC, ::AbstractTensorMap{S}, ::Index2Tuple{N₁,N₂}, + ::Symbol) where {S,N₁,N₂} + return tensormaptype(S, N₁, N₂, TC) +end + +function TO.tensoradd_structure(A::AbstractTensorMap{S}, pA::Index2Tuple{N₁,N₂}, + conjA::Symbol) where {S,N₁,N₂} + if conjA == :N + cod = ProductSpace{S,N₁}(space.(Ref(A), pA[1])) + dom = ProductSpace{S,N₂}(dual.(space.(Ref(A), pA[2]))) + return dom → cod else - error("unknown conjugation flags: $CA and $CB") + return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pA), :N) end - return tC end + +function TO.tensorcontract_type(TC, ::Index2Tuple{N₁,N₂}, + ::AbstractTensorMap{S}, pA, conjA, + ::AbstractTensorMap{S}, pB, conjB) where {S,N₁,N₂} + return tensormaptype(S, N₁, N₂, TC) +end + +function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, pA::Index2Tuple, + conjA, B::AbstractTensorMap, + pB::Index2Tuple, conjB) where {S,N₁,N₂} + spaces1 = conjA == :N ? space.(Ref(A), pA[1]) : + space.(Ref(A'), adjointtensorindices(A, pA[1])) + spaces2 = conjB == :N ? space.(Ref(B), pB[2]) : + space.(Ref(B'), adjointtensorindices(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 +end + +TO.tensorstructure(t::AbstractTensorMap) = space(t) +function TO.tensorstructure(::AbstractTensorMap, iA::Int, conjA::Symbol) + return conjA == :N ? space(A, iA) : space(A', iA) +end + +function TO.tensoralloc(ttype::Type{<:AbstractTensorMap}, structure, istemp=false) + return TensorMap(undef, scalartype(ttype), structure) +end + +VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = eltype(T) \ No newline at end of file diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index f6023a16..51646f05 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -64,7 +64,7 @@ ti = time() if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af1 = convert(Array, f1) Af2 = convert(Array, f2) - Af = TensorOperations.tensorcontract(Af1, [1:i-1; -1; N-1 .+ (i+1:N+1)], + Af = tensorcontract(Af1, [1:i-1; -1; N-1 .+ (i+1:N+1)], Af2, [i-1 .+ (1:N); -1], 1:2N) Af′ = zero(Af) for (f, coeff) in trees @@ -88,13 +88,9 @@ ti = time() for i = 1:N d = @constinferred TK.elementary_trace(f, i) j = mod1(i+1, N) - if j > i - oind = tuple(vcat(1:(i-1), j+1:N, N+1)...) - else - oind = tuple(vcat(2:N-1, N+1)...) - end - bf = TensorOperations.similar_from_indices(T, oind, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf, oind, (), (i,), (j,)) + inds = collect(1:N+1) + inds[i] = inds[j] + bf = tensortrace(af, inds) bf′ = zero(bf) for (f′, coeff) in d bf′ .+= coeff .* convert(Array, f′) @@ -104,8 +100,7 @@ ti = time() d2 = @constinferred TK.planar_trace(f, (1, 3), (2, 4)) oind2 = (5, 6, 7) - bf2 = TensorOperations.similar_from_indices(T, oind2, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf2, oind2, (), (1, 3), (2, 4)) + bf2 = tensortrace(af, (:a, :a, :b, :b, :c, :d, :e)) bf2′ = zero(bf2) for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) @@ -114,28 +109,16 @@ ti = time() d2 = @constinferred TK.planar_trace(f, (5, 6), (2, 1)) oind2 = (3, 4, 7) - bf2 = TensorOperations.similar_from_indices(T, oind2, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf2, oind2, (), (5, 6), (2, 1)) + bf2 = tensortrace(af, (:a, :b, :c, :d, :b, :a, :e)) bf2′ = zero(bf2) for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) end @test bf2 ≈ bf2′ atol=1e-12 - d2 = @constinferred TK.planar_trace(f, (5, 6), (2, 1)) - oind2 = (3, 4, 7) - bf2 = TensorOperations.similar_from_indices(T, oind2, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf2, oind2, (), (5, 6), (2, 1)) - bf2′ = zero(bf2) - for (f2′, coeff) in d2 - bf2′ .+= coeff .* convert(Array, f2′) - end - @test bf2 ≈ bf2′ atol=1e-12 d2 = @constinferred TK.planar_trace(f, (1, 4), (6, 3)) - oind2 = (2, 5, 7) - bf2 = TensorOperations.similar_from_indices(T, oind2, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf2, oind2, (), (6, 3), (1, 4)) + bf2 = tensortrace(af, (:a, :b, :c, :c, :d, :a, :e)) bf2′ = zero(bf2) for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) @@ -145,9 +128,7 @@ ti = time() q1 = (1, 3, 5) q2 = (2, 4, 6) d3 = @constinferred TK.planar_trace(f, q1, q2) - oind3 = (7,) - bf3 = TensorOperations.similar_from_indices(T, oind3, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf3, oind3, (), q1, q2) + bf3 = tensortrace(af, (:a, :a, :b, :b, :c, :c, :d)) bf3′ = zero(bf3) for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) @@ -157,9 +138,7 @@ ti = time() q1 = (1, 3, 5) q2 = (6, 2, 4) d3 = @constinferred TK.planar_trace(f, q1, q2) - oind3 = (7,) - bf3 = TensorOperations.similar_from_indices(T, oind3, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf3, oind3, (), q1, q2) + bf3 = tensortrace(af, (:a, :b, :b, :c, :c, :a, :d)) bf3′ = zero(bf3) for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) @@ -169,9 +148,7 @@ ti = time() q1 = (1, 2, 3) q2 = (6, 5, 4) d3 = @constinferred TK.planar_trace(f, q1, q2) - oind3 = (7,) - bf3 = TensorOperations.similar_from_indices(T, oind3, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf3, oind3, (), q1, q2) + bf3 = tensortrace(af, (:a, :b, :c, :c, :b, :a, :d)) bf3′ = zero(bf3) for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) @@ -181,9 +158,7 @@ ti = time() q1 = (1, 2, 4) q2 = (6, 3, 5) d3 = @constinferred TK.planar_trace(f, q1, q2) - oind3 = (7,) - bf3 = TensorOperations.similar_from_indices(T, oind3, (), af, :N) - TensorOperations.trace!(1, af, :N, 0, bf3, oind3, (), q1, q2) + bf3 = tensortrace(af, (:a, :b, :b, :c, :c, :a, :d)) bf3′ = zero(bf3) for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) diff --git a/test/runtests.jl b/test/runtests.jl index 380f8aa5..23d2ce8f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,9 +3,8 @@ using TestExtras using Random using TensorKit using Combinatorics -using TensorKit: ProductSector, fusiontensor +using TensorKit: ProductSector, fusiontensor, pentagon_equation, hexagon_equation using TensorOperations -TensorOperations.disable_cache() # avoids memory overflow during CI? using Base.Iterators: take, product using SUNRepresentations: SUNIrrep const SU3Irrep = SUNIrrep{3} diff --git a/test/sectors.jl b/test/sectors.jl index 06a429ee..cfa548fa 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -2,13 +2,14 @@ println("------------------------------------") println("Sectors") println("------------------------------------") ti = time() + @timedtestset "Sector properties of $(TensorKit.type_repr(I))" for I in sectorlist Istr = TensorKit.type_repr(I) @testset "Sector $Istr: Basic properties" begin s = (randsector(I), randsector(I), randsector(I)) @test eval(Meta.parse(sprint(show, I))) == I @test eval(Meta.parse(TensorKit.type_repr(I))) == I - @test eval(Meta.parse(sprint(show,s[1]))) == s[1] + @test eval(Meta.parse(sprint(show, s[1]))) == s[1] @test @constinferred(hash(s[1])) == hash(deepcopy(s[1])) @test @constinferred(one(s[1])) == @constinferred(one(I)) @constinferred dual(s[1]) @@ -29,7 +30,7 @@ ti = time() if Base.IteratorSize(values(I)) == Base.IsInfinite() && I <: ProductSector @test_throws ArgumentError values(I)[i] @test_throws ArgumentError TensorKit.findindex(values(I), s) - elseif hasmethod(Base.getindex, Tuple{typeof(values(I)), Int}) + elseif hasmethod(Base.getindex, Tuple{typeof(values(I)),Int}) @test s == @constinferred (values(I)[i]) @test TensorKit.findindex(values(I), s) == i end @@ -39,7 +40,7 @@ ti = time() @test one(I) == first(values(I)) if Base.IteratorSize(values(I)) == Base.IsInfinite() && I <: ProductSector @test_throws ArgumentError TensorKit.findindex(values(I), one(I)) - elseif hasmethod(Base.getindex, Tuple{typeof(values(I)), Int}) + elseif hasmethod(Base.getindex, Tuple{typeof(values(I)),Int}) @test (@constinferred TensorKit.findindex(values(I), one(I))) == 1 for s in smallset(I) @test (@constinferred values(I)[TensorKit.findindex(values(I), s)]) == s @@ -49,10 +50,10 @@ ti = time() if hasfusiontensor(I) @testset "Sector $I: fusion tensor and F-move and R-move" begin for a in smallset(I), b in smallset(I) - for c in ⊗(a,b) + for c in ⊗(a, b) X1 = permutedims(fusiontensor(a, b, c), (2, 1, 3, 4)) X2 = fusiontensor(b, a, c) - l =dim(a)*dim(b)*dim(c) + l = dim(a) * dim(b) * dim(c) R = LinearAlgebra.transpose(Rsymbol(a, b, c)) sz = (l, convert(Int, Nsymbol(a, b, c))) @test reshape(X1, sz) ≈ reshape(X2, sz) * R @@ -65,14 +66,15 @@ ti = time() X2 = fusiontensor(e, c, d) Y1 = fusiontensor(b, c, f) Y2 = fusiontensor(a, f, d) - @tensor f1[-1,-2,-3,-4] := conj(Y2[a,f,d,-4])*conj(Y1[b,c,f,-3])* - X1[a,b,e,-1] * X2[e,c,d,-2] + @tensor f1[-1, -2, -3, -4] := conj(Y2[a, f, d, -4]) * + conj(Y1[b, c, f, -3]) * + X1[a, b, e, -1] * X2[e, c, d, -2] if FusionStyle(I) isa MultiplicityFreeFusion - f2 = fill(Fsymbol(a,b,c,d,e,f)*dim(d), (1,1,1,1)) + f2 = fill(Fsymbol(a, b, c, d, e, f) * dim(d), (1, 1, 1, 1)) else - f2 = Fsymbol(a,b,c,d,e,f)*dim(d) + f2 = Fsymbol(a, b, c, d, e, f) * dim(d) end - @test isapprox(f1, f2; atol = 1e-12, rtol = 1e-12) + @test isapprox(f1, f2; atol=1e-12, rtol=1e-12) end end end @@ -80,86 +82,42 @@ ti = time() end @testset "Sector $Istr: Unitarity of F-move" begin for a in smallset(I), b in smallset(I), c in smallset(I) - for d in ⊗(a,b,c) - es = collect(intersect(⊗(a,b), map(dual, ⊗(c,dual(d))))) - fs = collect(intersect(⊗(b,c), map(dual, ⊗(dual(d),a)))) + for d in ⊗(a, b, c) + es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) + fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) if FusionStyle(I) isa MultiplicityFreeFusion @test length(es) == length(fs) - F = [Fsymbol(a,b,c,d,e,f) for e in es, f in fs] + F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] else Fblocks = Vector{Any}() for e in es for f in fs - Fs = Fsymbol(a,b,c,d,e,f) - push!(Fblocks, reshape(Fs, (size(Fs, 1)*size(Fs, 2), size(Fs, 3)*size(Fs, 4)))) + Fs = Fsymbol(a, b, c, d, e, f) + push!(Fblocks, + reshape(Fs, + (size(Fs, 1) * size(Fs, 2), + size(Fs, 3) * size(Fs, 4)))) end end F = hvcat(length(fs), Fblocks...) end - @test isapprox(F'*F, one(F); atol = 1e-12, rtol = 1e-12) + @test isapprox(F' * F, one(F); atol=1e-12, rtol=1e-12) end end end @testset "Sector $Istr: Pentagon equation" begin for a in smallset(I), b in smallset(I), c in smallset(I), d in smallset(I) - for f in ⊗(a,b), h in ⊗(c,d) - for g in ⊗(f,c), i in ⊗(b,h) - for e in intersect(⊗(g,d), ⊗(a,i)) - if FusionStyle(I) isa MultiplicityFreeFusion - p1 = Fsymbol(f,c,d,e,g,h) * Fsymbol(a,b,h,e,f,i) - p2 = zero(p1) - for j in ⊗(b,c) - p2 += Fsymbol(a,b,c,g,f,j) * - Fsymbol(a,j,d,e,g,i) * - Fsymbol(b,c,d,i,j,h) - end - @test isapprox(p1, p2; atol = 1e-12, rtol = 1e-12) - else - @tensor p1[λ,μ,ν,κ,ρ,σ] := Fsymbol(f,c,d,e,g,h)[λ,μ,ν,τ]* - Fsymbol(a,b,h,e,f,i)[κ,τ,ρ,σ] - p2 = zero(p1) - for j in ⊗(b,c) - @tensor p2[λ,μ,ν,κ,ρ,σ] += Fsymbol(a,b,c,g,f,j)[κ,λ,α,β]* - Fsymbol(a,j,d,e,g,i)[β,μ,τ,σ]* - Fsymbol(b,c,d,i,j,h)[α,τ,ν,ρ] - end - @test isapprox(p1, p2; atol = 1e-12, rtol = 1e-12) - end - end - end - end + @test pentagon_equation(a, b, c, d; atol=1e-12, rtol=1e-12) end end @testset "Sector $Istr: Hexagon equation" begin for a in smallset(I), b in smallset(I), c in smallset(I) - for e in ⊗(c,a), g in ⊗(c,b) - for d in intersect(⊗(e,b), ⊗(a,g)) - if FusionStyle(I) isa MultiplicityFreeFusion - p1 = Rsymbol(c,a,e)*Fsymbol(a,c,b,d,e,g)*Rsymbol(b,c,g) - p2 = zero(p1) - for f in ⊗(a,b) - p2 += Fsymbol(c,a,b,d,e,f)*Rsymbol(c,f,d)*Fsymbol(a,b,c,d,f,g) - end - @test isapprox(p1, p2; atol = 1e-12, rtol = 1e-12) - else - @tensor p1[α,β,μ,ν] := Rsymbol(c,a,e)[α,λ]* - Fsymbol(a,c,b,d,e,g)[λ,β,γ,ν]* - Rsymbol(b,c,g)[γ,μ] - p2 = zero(p1) - for f in ⊗(a,b) - @tensor p2[α,β,μ,ν] += Fsymbol(c,a,b,d,e,f)[α,β,δ,σ]* - Rsymbol(c,f,d)[σ,ψ]* - Fsymbol(a,b,c,d,f,g)[δ,ψ,μ,ν] - end - @test isapprox(p1, p2; atol = 1e-12, rtol = 1e-12) - end - end - end + @test hexagon_equation(a, b, c; atol=1e-12, rtol=1e-12) end end end tf = time() printstyled("Finished sector tests in ", - string(round(tf-ti; sigdigits=3)), - " seconds."; bold = true, color = Base.info_color()) + string(round(tf - ti; sigdigits=3)), + " seconds."; bold=true, color=Base.info_color()) println() From 588e149fe0f0eb2ea0da873ee63d1e6a04c35014 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Jun 2023 09:58:14 +0200 Subject: [PATCH 06/57] Format tests, Fix TensorOperations argument order --- test/braidingtensor.jl | 139 +++++++-------- test/fusiontrees.jl | 321 +++++++++++++++++---------------- test/newsectors.jl | 32 ++-- test/runtests.jl | 10 +- test/spaces.jl | 160 +++++++++-------- test/tensors.jl | 396 +++++++++++++++++++++-------------------- 6 files changed, 545 insertions(+), 513 deletions(-) diff --git a/test/braidingtensor.jl b/test/braidingtensor.jl index 67aa82f7..b61abc61 100644 --- a/test/braidingtensor.jl +++ b/test/braidingtensor.jl @@ -2,8 +2,7 @@ import TensorKit: BraidingTensor - -V1 = GradedSpace{FermionSpin}(0=>2, 1/2=>2, 1=>1, 3/2=>1) +V1 = GradedSpace{FermionSpin}(0 => 2, 1 / 2 => 2, 1 => 1, 3 / 2 => 1) V2 = GradedSpace{FibonacciAnyon}(:I => 2, :τ => 2) @@ -15,121 +14,121 @@ for V in (V1, V2, V3) t = TensorMap(randn, V * V' * V' * V, V * V') ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-1 -2; 1 2]*t[1 2 -3 -4; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-1 -2; 1 2]*t[1 2 -3 -4; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -2 -1]*t[1 2 -3 -4; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-2 2; -1 1]*t[1 2 -3 -4; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-1 -2; 1 2] * t[1 2 -3 -4; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-1 -2; 1 2] * t[1 2 -3 -4; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -2 -1] * t[1 2 -3 -4; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-2 2; -1 1] * t[1 2 -3 -4; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V')) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-2 -3; 1 2]*t[-1 1 2 -4; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-2 -3; 1 2]*t[-1 1 2 -4; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -3 -2]*t[-1 1 2 -4; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-3 2; -2 1]*t[-1 1 2 -4; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-2 -3; 1 2] * t[-1 1 2 -4; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-2 -3; 1 2] * t[-1 1 2 -4; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -3 -2] * t[-1 1 2 -4; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-3 2; -2 1] * t[-1 1 2 -4; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[1 -2; 2 -3]*t[-1 1 2 -4; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[1 -2; 2 -3]*t[-1 1 2 -4; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[-3 2; -2 1]*t[-1 1 2 -4; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-2 -3; 1 2]*t[-1 1 2 -4; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[1 -2; 2 -3] * t[-1 1 2 -4; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[1 -2; 2 -3] * t[-1 1 2 -4; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[-3 2; -2 1] * t[-1 1 2 -4; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-2 -3; 1 2] * t[-1 1 2 -4; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-3 2; -2 1]*t[-1 1 2 -4; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-3 2; -2 1]*t[-1 1 2 -4; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[1 -2; 2 -3]*t[-1 1 2 -4; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[2 1; -3 -2]*t[-1 1 2 -4; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-3 2; -2 1] * t[-1 1 2 -4; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-3 2; -2 1] * t[-1 1 2 -4; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[1 -2; 2 -3] * t[-1 1 2 -4; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[2 1; -3 -2] * t[-1 1 2 -4; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V)) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[2 1; -3 -2]*t[-1 1 2 -4; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[2 1; -3 -2]*t[-1 1 2 -4; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[-2 -3; 1 2]*t[-1 1 2 -4; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[1 -2; 2 -3]*t[-1 1 2 -4; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[2 1; -3 -2] * t[-1 1 2 -4; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[2 1; -3 -2] * t[-1 1 2 -4; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[-2 -3; 1 2] * t[-1 1 2 -4; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[1 -2; 2 -3] * t[-1 1 2 -4; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-3 -4; 1 2]*t[-1 -2 1 2; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-3 -4; 1 2]*t[-1 -2 1 2; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -4 -3]*t[-1 -2 1 2; -5 -6] - @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-4 2; -3 1]*t[-1 -2 1 2; -5 -6] + @planar2 t1[-1 -2 -3 -4; -5 -6] := τ[-3 -4; 1 2] * t[-1 -2 1 2; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := ττ[-3 -4; 1 2] * t[-1 -2 1 2; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := τ[2 1; -4 -3] * t[-1 -2 1 2; -5 -6] + @planar2 t4[-1 -2 -3 -4; -5 -6] := τ'[-4 2; -3 1] * t[-1 -2 1 2; -5 -6] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ[1 2; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*ττ[1 2; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ[-6 -5; 2 1] - @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ'[2 -6; 1 -5] + @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ[1 2; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * ττ[1 2; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ[-6 -5; 2 1] + @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ'[2 -6; 1 -5] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ'[1 2; -5 -6] - @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*ττ'[1 2; -5 -6] - @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ'[-6 -5; 2 1] - @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2]*τ[2 -6; 1 -5] + @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ'[1 2; -5 -6] + @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * ττ'[1 2; -5 -6] + @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ'[-6 -5; 2 1] + @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 -4; 1 2] * τ[2 -6; 1 -5] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V)) - @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2]*τ[-4 -6; 1 2] - @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2]*ττ[-4 -6; 1 2] - @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2]*τ[2 1; -6 -4] - @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2]*τ'[-6 2; -4 1] + @planar2 t1[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2] * τ[-4 -6; 1 2] + @planar2 t2[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2] * ττ[-4 -6; 1 2] + @planar2 t3[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2] * τ[2 1; -6 -4] + @planar2 t4[-1 -2 -3 -4; -5 -6] := t[-1 -2 -3 1; -5 2] * τ'[-6 2; -4 1] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[();(-1,-2)] := τ[2 1; 3 4]*t[1 2 3 4; -1 -2] - @planar2 t2[();(-1,-2)] := ττ[2 1; 3 4]*t[1 2 3 4; -1 -2] - @planar2 t3[();(-1,-2)] := τ[4 3; 1 2]*t[1 2 3 4; -1 -2] - @planar2 t4[();(-1,-2)] := τ'[1 4; 2 3]*t[1 2 3 4; -1 -2] + @planar2 t1[(); (-1, -2)] := τ[2 1; 3 4] * t[1 2 3 4; -1 -2] + @planar2 t2[(); (-1, -2)] := ττ[2 1; 3 4] * t[1 2 3 4; -1 -2] + @planar2 t3[(); (-1, -2)] := τ[4 3; 1 2] * t[1 2 3 4; -1 -2] + @planar2 t4[(); (-1, -2)] := τ'[1 4; 2 3] * t[1 2 3 4; -1 -2] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V)) - @planar2 t1[-1; -2] := τ[2 1; 3 4]*t[-1 1 2 3; -2 4] - @planar2 t2[-1; -2] := ττ[2 1; 3 4]*t[-1 1 2 3; -2 4] - @planar2 t3[-1; -2] := τ[4 3; 1 2]*t[-1 1 2 3; -2 4] - @planar2 t4[-1; -2] := τ'[1 4; 2 3]*t[-1 1 2 3; -2 4] + @planar2 t1[-1; -2] := τ[2 1; 3 4] * t[-1 1 2 3; -2 4] + @planar2 t2[-1; -2] := ττ[2 1; 3 4] * t[-1 1 2 3; -2 4] + @planar2 t3[-1; -2] := τ[4 3; 1 2] * t[-1 1 2 3; -2 4] + @planar2 t4[-1; -2] := τ'[1 4; 2 3] * t[-1 1 2 3; -2 4] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2] := τ[2 1; 3 4]*t[-1 -2 1 2; 4 3] - @planar2 t2[-1 -2] := ττ[2 1; 3 4]*t[-1 -2 1 2; 4 3] - @planar2 t3[-1 -2] := τ[4 3; 1 2]*t[-1 -2 1 2; 4 3] - @planar2 t4[-1 -2] := τ'[1 4; 2 3]*t[-1 -2 1 2; 4 3] + @planar2 t1[-1 -2] := τ[2 1; 3 4] * t[-1 -2 1 2; 4 3] + @planar2 t2[-1 -2] := ττ[2 1; 3 4] * t[-1 -2 1 2; 4 3] + @planar2 t3[-1 -2] := τ[4 3; 1 2] * t[-1 -2 1 2; 4 3] + @planar2 t4[-1 -2] := τ'[1 4; 2 3] * t[-1 -2 1 2; 4 3] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2; -3 -4] := τ[-1 3; 1 2]*t[1 2 3 -2; -3 -4] - @planar2 t2[-1 -2; -3 -4] := ττ[-1 3; 1 2]*t[1 2 3 -2; -3 -4] - @planar2 t3[-1 -2; -3 -4] := τ[2 1; 3 -1]*t[1 2 3 -2; -3 -4] - @planar2 t4[-1 -2; -3 -4] := τ'[3 2; -1 1]*t[1 2 3 -2; -3 -4] + @planar2 t1[-1 -2; -3 -4] := τ[-1 3; 1 2] * t[1 2 3 -2; -3 -4] + @planar2 t2[-1 -2; -3 -4] := ττ[-1 3; 1 2] * t[1 2 3 -2; -3 -4] + @planar2 t3[-1 -2; -3 -4] := τ[2 1; 3 -1] * t[1 2 3 -2; -3 -4] + @planar2 t4[-1 -2; -3 -4] := τ'[3 2; -1 1] * t[1 2 3 -2; -3 -4] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V')) - @planar2 t1[-1 -2; -3 -4] := τ'[-2 3; 1 2]*t[-1 1 2 3; -3 -4] - @planar2 t2[-1 -2; -3 -4] := ττ'[-2 3; 1 2]*t[-1 1 2 3; -3 -4] - @planar2 t3[-1 -2; -3 -4] := τ'[2 1; 3 -2]*t[-1 1 2 3; -3 -4] - @planar2 t4[-1 -2; -3 -4] := τ[3 2; -2 1]*t[-1 1 2 3; -3 -4] + @planar2 t1[-1 -2; -3 -4] := τ'[-2 3; 1 2] * t[-1 1 2 3; -3 -4] + @planar2 t2[-1 -2; -3 -4] := ττ'[-2 3; 1 2] * t[-1 1 2 3; -3 -4] + @planar2 t3[-1 -2; -3 -4] := τ'[2 1; 3 -2] * t[-1 1 2 3; -3 -4] + @planar2 t4[-1 -2; -3 -4] := τ[3 2; -2 1] * t[-1 1 2 3; -3 -4] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[-1 -2 -3; -4] := τ[-3 3; 1 2]*t[-1 -2 1 2; -4 3] - @planar2 t2[-1 -2 -3; -4] := ττ[-3 3; 1 2]*t[-1 -2 1 2; -4 3] - @planar2 t3[-1 -2 -3; -4] := τ[2 1; 3 -3]*t[-1 -2 1 2; -4 3] - @planar2 t4[-1 -2 -3; -4] := τ'[3 2; -3 1]*t[-1 -2 1 2; -4 3] + @planar2 t1[-1 -2 -3; -4] := τ[-3 3; 1 2] * t[-1 -2 1 2; -4 3] + @planar2 t2[-1 -2 -3; -4] := ττ[-3 3; 1 2] * t[-1 -2 1 2; -4 3] + @planar2 t3[-1 -2 -3; -4] := τ[2 1; 3 -3] * t[-1 -2 1 2; -4 3] + @planar2 t4[-1 -2 -3; -4] := τ'[3 2; -3 1] * t[-1 -2 1 2; -4 3] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V', V)) - @planar2 t1[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ[1 2; -4 3] - @planar2 t2[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*ττ[1 2; -4 3] - @planar2 t3[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ[3 -4; 2 1] - @planar2 t4[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ'[2 3; 1 -4] + @planar2 t1[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ[1 2; -4 3] + @planar2 t2[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * ττ[1 2; -4 3] + @planar2 t3[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ[3 -4; 2 1] + @planar2 t4[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ'[2 3; 1 -4] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) ττ = copy(BraidingTensor(V, V')) - @planar2 t1[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ'[1 2; -4 3] - @planar2 t2[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*ττ'[1 2; -4 3] - @planar2 t3[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ'[3 -4; 2 1] - @planar2 t4[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2]*τ[2 3; 1 -4] + @planar2 t1[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ'[1 2; -4 3] + @planar2 t2[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * ττ'[1 2; -4 3] + @planar2 t3[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ'[3 -4; 2 1] + @planar2 t4[-1 -2 -3; -4] := t[-1 -2 -3 3; 1 2] * τ[2 3; 1 -4] @show norm(t1 - t2), norm(t1 - t3), norm(t1 - t4) end diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index 51646f05..f2840399 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -5,14 +5,14 @@ ti = time() @timedtestset "Fusion trees for $(TensorKit.type_repr(I))" for I in sectorlist Istr = TensorKit.type_repr(I) N = 5 - out = ntuple(n->randsector(I), N) - isdual = ntuple(n->rand(Bool), N) + out = ntuple(n -> randsector(I), N) + isdual = ntuple(n -> rand(Bool), N) in = rand(collect(⊗(out...))) - numtrees = count(n->true, fusiontrees(out, in, isdual)) + numtrees = count(n -> true, fusiontrees(out, in, isdual)) while !(0 < numtrees < 30) - out = ntuple(n->randsector(I), N) + out = ntuple(n -> randsector(I), N) in = rand(collect(⊗(out...))) - numtrees = count(n->true, fusiontrees(out, in, isdual)) + numtrees = count(n -> true, fusiontrees(out, in, isdual)) end it = @constinferred fusiontrees(out, in, isdual) @constinferred Nothing iterate(it) @@ -22,15 +22,15 @@ ti = time() end @testset "Fusion tree $Istr: insertat" begin N = 4 - out2 = ntuple(n->randsector(I), N) + out2 = ntuple(n -> randsector(I), N) in2 = rand(collect(⊗(out2...))) - isdual2 = ntuple(n->rand(Bool), N) + isdual2 = ntuple(n -> rand(Bool), N) f2 = rand(collect(fusiontrees(out2, in2, isdual2))) - for i = 1:N - out1 = ntuple(n->randsector(I), N) + for i in 1:N + out1 = ntuple(n -> randsector(I), N) out1 = Base.setindex(out1, in2, i) in1 = rand(collect(⊗(out1...))) - isdual1 = ntuple(n->rand(Bool), N) + isdual1 = ntuple(n -> rand(Bool), N) isdual1 = Base.setindex(isdual1, false, i) f1 = rand(collect(fusiontrees(out1, in1, isdual1))) @@ -42,35 +42,37 @@ ti = time() @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1) levels = ntuple(identity, N) - gen = Base.Generator(braid(f1, levels, (i, (1:i-1)..., (i+1:N)...))) do (t, c) + gen = Base.Generator(braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...))) do (t, + c) (t′, c′) = first(TK.insertat(t, 1, f2)) @test c′ == one(c′) return t′ => c end trees2 = Dict(gen) trees3 = empty(trees2) - p = ((N+1:N+i-1)..., (1:N)..., (N+i:2N-1)...) - levels = ((i:N+i-1)..., (1:i-1)..., (i+N:2N-1)...) - for (t,coeff) in trees2 - for (t′,coeff′) in braid(t, levels, p) - trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff*coeff′ + p = (((N + 1):(N + i - 1))..., (1:N)..., ((N + i):(2N - 1))...) + levels = ((i:(N + i - 1))..., (1:(i - 1))..., ((i + N):(2N - 1))...) + for (t, coeff) in trees2 + for (t′, coeff′) in braid(t, levels, p) + trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff * coeff′ end end for (t, coeff) in trees3 coeff′ = get(trees, t, zero(coeff)) - @test isapprox(coeff′, coeff; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff′, coeff; atol=1e-12, rtol=1e-12) end if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af1 = convert(Array, f1) Af2 = convert(Array, f2) - Af = tensorcontract(Af1, [1:i-1; -1; N-1 .+ (i+1:N+1)], - Af2, [i-1 .+ (1:N); -1], 1:2N) + Af = tensorcontract(1:(2N), Af1, + [1:(i - 1); -1; N - 1 .+ ((i + 1):(N + 1))], + Af2, [i - 1 .+ (1:N); -1]) Af′ = zero(Af) for (f, coeff) in trees Af′ .+= coeff .* convert(Array, f) end - @test isapprox(Af, Af′; atol = 1e-12, rtol = 1e-12) + @test isapprox(Af, Af′; atol=1e-12, rtol=1e-12) end end end @@ -85,17 +87,17 @@ ti = time() af = convert(Array, f) T = eltype(af) - for i = 1:N + for i in 1:N d = @constinferred TK.elementary_trace(f, i) - j = mod1(i+1, N) - inds = collect(1:N+1) + j = mod1(i + 1, N) + inds = collect(1:(N + 1)) inds[i] = inds[j] bf = tensortrace(af, inds) bf′ = zero(bf) for (f′, coeff) in d bf′ .+= coeff .* convert(Array, f′) end - @test bf ≈ bf′ atol=1e-12 + @test bf ≈ bf′ atol = 1e-12 end d2 = @constinferred TK.planar_trace(f, (1, 3), (2, 4)) @@ -105,7 +107,7 @@ ti = time() for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) end - @test bf2 ≈ bf2′ atol=1e-12 + @test bf2 ≈ bf2′ atol = 1e-12 d2 = @constinferred TK.planar_trace(f, (5, 6), (2, 1)) oind2 = (3, 4, 7) @@ -114,8 +116,7 @@ ti = time() for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) end - @test bf2 ≈ bf2′ atol=1e-12 - + @test bf2 ≈ bf2′ atol = 1e-12 d2 = @constinferred TK.planar_trace(f, (1, 4), (6, 3)) bf2 = tensortrace(af, (:a, :b, :c, :c, :d, :a, :e)) @@ -123,7 +124,7 @@ ti = time() for (f2′, coeff) in d2 bf2′ .+= coeff .* convert(Array, f2′) end - @test bf2 ≈ bf2′ atol=1e-12 + @test bf2 ≈ bf2′ atol = 1e-12 q1 = (1, 3, 5) q2 = (2, 4, 6) @@ -133,7 +134,7 @@ ti = time() for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) end - @test bf3 ≈ bf3′ atol=1e-12 + @test bf3 ≈ bf3′ atol = 1e-12 q1 = (1, 3, 5) q2 = (6, 2, 4) @@ -143,7 +144,7 @@ ti = time() for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) end - @test bf3 ≈ bf3′ atol=1e-12 + @test bf3 ≈ bf3′ atol = 1e-12 q1 = (1, 2, 3) q2 = (6, 5, 4) @@ -153,7 +154,7 @@ ti = time() for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) end - @test bf3 ≈ bf3′ atol=1e-12 + @test bf3 ≈ bf3′ atol = 1e-12 q1 = (1, 2, 4) q2 = (6, 3, 5) @@ -163,30 +164,30 @@ ti = time() for (f3′, coeff) in d3 bf3′ .+= coeff .* convert(Array, f3′) end - @test bf3 ≈ bf3′ atol=1e-12 + @test bf3 ≈ bf3′ atol = 1e-12 end end end end @testset "Fusion tree $Istr: elementy artin braid" begin N = length(out) - isdual = ntuple(n->rand(Bool), N) - for in = ⊗(out...) - for i = 1:N-1 + isdual = ntuple(n -> rand(Bool), N) + for in in ⊗(out...) + for i in 1:(N - 1) for f in fusiontrees(out, in, isdual) d1 = @constinferred TK.artin_braid(f, i) @test norm(values(d1)) ≈ 1 d2 = empty(d1) for (f1, coeff1) in d1 - for (f2,coeff2) in TK.artin_braid(f1, i; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1 + for (f2, coeff2) in TK.artin_braid(f1, i; inv=true) + d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 end end for (f2, coeff2) in d2 if f2 == f @test coeff2 ≈ 1 else - @test isapprox(coeff2, 0; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff2, 0; atol=1e-12, rtol=1e-12) end end end @@ -197,22 +198,22 @@ ti = time() d1 = TK.artin_braid(f, 2) d2 = empty(d1) for (f1, coeff1) in d1 - for (f2,coeff2) in TK.artin_braid(f1, 3) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1 + for (f2, coeff2) in TK.artin_braid(f1, 3) + d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 end end d1 = d2 d2 = empty(d1) for (f1, coeff1) in d1 - for (f2,coeff2) in TK.artin_braid(f1, 3; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1 + for (f2, coeff2) in TK.artin_braid(f1, 3; inv=true) + d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 end end d1 = d2 d2 = empty(d1) for (f1, coeff1) in d1 - for (f2,coeff2) in TK.artin_braid(f1, 2; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2*coeff1 + for (f2, coeff2) in TK.artin_braid(f1, 2; inv=true) + d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 end end d1 = d2 @@ -220,35 +221,35 @@ ti = time() if f1 == f @test coeff1 ≈ 1 else - @test isapprox(coeff1, 0; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff1, 0; atol=1e-12, rtol=1e-12) end end end @testset "Fusion tree $Istr: braiding and permuting" begin f = rand(collect(fusiontrees(out, in, isdual))) - p = tuple(randperm(N)...,) + p = tuple(randperm(N)...) ip = invperm(p) levels = ntuple(identity, N) d = @constinferred braid(f, levels, p) - d2 = Dict{typeof(f), valtype(d)}() + d2 = Dict{typeof(f),valtype(d)}() levels2 = p for (f2, coeff) in d - for (f1,coeff2) in braid(f2, levels2, ip) - d2[f1] = get(d2, f1, zero(coeff)) + coeff2*coeff + for (f1, coeff2) in braid(f2, levels2, ip) + d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff end end for (f1, coeff2) in d2 if f1 == f @test coeff2 ≈ 1 else - @test isapprox(coeff2, 0; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff2, 0; atol=1e-12, rtol=1e-12) end end if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af = convert(Array, f) - Afp = permutedims(Af, (p..., N+1)) + Afp = permutedims(Af, (p..., N + 1)) Afp2 = zero(Afp) for (f1, coeff) in d Afp2 .+= coeff .* convert(Array, f1) @@ -259,10 +260,10 @@ ti = time() @testset "Fusion tree $Istr: merging" begin N = 3 - out1 = ntuple(n->randsector(I), N) + out1 = ntuple(n -> randsector(I), N) in1 = rand(collect(⊗(out1...))) f1 = rand(collect(fusiontrees(out1, in1))) - out2 = ntuple(n->randsector(I), N) + out2 = ntuple(n -> randsector(I), N) in2 = rand(collect(⊗(out2...))) f2 = rand(collect(fusiontrees(out2, in2))) @@ -271,46 +272,48 @@ ti = time() @constinferred TK.merge(f1, f2, first(in1 ⊗ in2), nothing) @constinferred TK.merge(f1, f2, first(in1 ⊗ in2)) end - @test dim(in1)*dim(in2) ≈ sum(abs2(coeff)*dim(c) for c in in1 ⊗ in2 - for μ in 1:Nsymbol(in1, in2, c) - for (f,coeff) in TK.merge(f1, f2, c, μ)) + @test dim(in1) * dim(in2) ≈ sum(abs2(coeff) * dim(c) for c in in1 ⊗ in2 + for μ in 1:Nsymbol(in1, in2, c) + for (f, coeff) in TK.merge(f1, f2, c, μ)) for c in in1 ⊗ in2 R = Rsymbol(in1, in2, c) - for μ = 1:Nsymbol(in1, in2, c) + for μ in 1:Nsymbol(in1, in2, c) μ′ = FusionStyle(I) isa GenericFusion ? μ : nothing trees1 = TK.merge(f1, f2, c, μ′) # test merge and braid interplay - trees2 = Dict{keytype(trees1), complex(valtype(trees1))}() - trees3 = Dict{keytype(trees1), complex(valtype(trees1))}() - for ν = 1:Nsymbol(in2, in1, c) + trees2 = Dict{keytype(trees1),complex(valtype(trees1))}() + trees3 = Dict{keytype(trees1),complex(valtype(trees1))}() + for ν in 1:Nsymbol(in2, in1, c) ν′ = FusionStyle(I) isa GenericFusion ? ν : nothing for (t, coeff) in TK.merge(f2, f1, c, ν′) - trees2[t] = get(trees2, t, zero(valtype(trees2))) + coeff*R[μ,ν] + trees2[t] = get(trees2, t, zero(valtype(trees2))) + coeff * R[μ, ν] end end - perm = ( (N.+(1:N))... , (1:N)...) - levels = ntuple(identity, 2*N) + perm = ((N .+ (1:N))..., (1:N)...) + levels = ntuple(identity, 2 * N) for (t, coeff) in trees1 for (t′, coeff′) in braid(t, levels, perm) - trees3[t′] = get(trees3, t′, zero(valtype(trees3))) + coeff*coeff′ + trees3[t′] = get(trees3, t′, zero(valtype(trees3))) + coeff * coeff′ end end for (t, coeff) in trees3 coeff′ = get(trees2, t, zero(coeff)) - @test isapprox(coeff, coeff′; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff, coeff′; atol=1e-12, rtol=1e-12) end # test via conversion if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af1 = convert(Array, f1) Af2 = convert(Array, f2) - Af0 = convert(Array, FusionTree((f1.coupled, f2.coupled), c, (false, false), (), (μ,))) - _Af = TensorOperations.tensorcontract(Af1, [1:N;-1], - Af0, [-1;N+1;N+2], 1:N+2) - Af = TensorOperations.tensorcontract(Af2, [N .+ (1:N); -1], - _Af, [1:N; -1; 2N+1], 1:2N+1) + Af0 = convert(Array, + FusionTree((f1.coupled, f2.coupled), c, (false, false), + (), (μ,))) + _Af = TensorOperations.tensorcontract(1:(N + 2), Af1, [1:N; -1], + Af0, [-1; N + 1; N + 2]) + Af = TensorOperations.tensorcontract(1:(2N + 1), Af2, [N .+ (1:N); -1], + _Af, [1:N; -1; 2N + 1]) Af′ = zero(Af) for (f, coeff) in trees1 Af′ .+= coeff .* convert(Array, f) @@ -326,54 +329,56 @@ ti = time() else N = 4 end - out = ntuple(n->randsector(I), N) - numtrees = count(n->true, fusiontrees((out..., map(dual, out)...))) + out = ntuple(n -> randsector(I), N) + numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) - out = ntuple(n->randsector(I), N) - numtrees = count(n->true, fusiontrees((out..., map(dual, out)...))) + out = ntuple(n -> randsector(I), N) + numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) end incoming = rand(collect(⊗(out...))) - f1 = rand(collect(fusiontrees(out, incoming, ntuple(n->rand(Bool), N)))) - f2 = rand(collect(fusiontrees(out[randperm(N)], incoming, ntuple(n->rand(Bool), N)))) + f1 = rand(collect(fusiontrees(out, incoming, ntuple(n -> rand(Bool), N)))) + f2 = rand(collect(fusiontrees(out[randperm(N)], incoming, ntuple(n -> rand(Bool), N)))) @testset "Double fusion tree $Istr: repartioning" begin - for n = 0:2*N + for n in 0:(2 * N) d = @constinferred TK.repartition(f1, f2, $n) - @test dim(incoming) ≈ sum(abs2(coef)*dim(f1.coupled) for ((f1,f2), coef) in d) - d2 = Dict{typeof((f1,f2)), valtype(d)}() - for ((f1′,f2′),coeff) in d - for ((f1′′,f2′′),coeff2) in TK.repartition(f1′,f2′, N) - d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff2*coeff + @test dim(incoming) ≈ + sum(abs2(coef) * dim(f1.coupled) for ((f1, f2), coef) in d) + d2 = Dict{typeof((f1, f2)),valtype(d)}() + for ((f1′, f2′), coeff) in d + for ((f1′′, f2′′), coeff2) in TK.repartition(f1′, f2′, N) + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff end end - for ((f1′,f2′), coeff2) in d2 + for ((f1′, f2′), coeff2) in d2 if f1 == f1′ && f2 == f2′ @test coeff2 ≈ 1 else - @test isapprox(coeff2, 0; atol = 1e-12, rtol = 1e-12) + @test isapprox(coeff2, 0; atol=1e-12, rtol=1e-12) end end if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af1 = convert(Array, f1) - Af2 = permutedims(convert(Array, f2), [N:-1:1; N+1]) + Af2 = permutedims(convert(Array, f2), [N:-1:1; N + 1]) sz1 = size(Af1) sz2 = size(Af2) - d1 = prod(sz1[1:end-1]) - d2 = prod(sz2[1:end-1]) + d1 = prod(sz1[1:(end - 1)]) + d2 = prod(sz2[1:(end - 1)]) dc = sz1[end] A = reshape(reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))', - (sz1[1:end-1]..., sz2[1:end-1]...)) + (sz1[1:(end - 1)]..., sz2[1:(end - 1)]...)) A2 = zero(A) - for ((f1′,f2′), coeff) in d + for ((f1′, f2′), coeff) in d Af1′ = convert(Array, f1′) - Af2′ = permutedims(convert(Array, f2′), [(2N-n):-1:1; 2N-n+1]) + Af2′ = permutedims(convert(Array, f2′), [(2N - n):-1:1; 2N - n + 1]) sz1′ = size(Af1′) sz2′ = size(Af2′) - d1′ = prod(sz1′[1:end-1]) - d2′ = prod(sz2′[1:end-1]) + d1′ = prod(sz1′[1:(end - 1)]) + d2′ = prod(sz2′[1:(end - 1)]) dc′ = sz1′[end] - A2 += coeff*reshape(reshape(Af1′,(d1′,dc′)) * reshape(Af2′,(d2′,dc′))', - (sz1′[1:end-1]..., sz2′[1:end-1]...)) + A2 += coeff * + reshape(reshape(Af1′, (d1′, dc′)) * reshape(Af2′, (d2′, dc′))', + (sz1′[1:(end - 1)]..., sz2′[1:(end - 1)]...)) end @test A ≈ A2 end @@ -381,22 +386,24 @@ ti = time() end @testset "Double fusion tree $Istr: permutation" begin if BraidingStyle(I) isa SymmetricBraiding - for n = 0:2N - p = (randperm(2*N)...,) - p1, p2 = p[1:n], p[n+1:2N] + for n in 0:(2N) + p = (randperm(2 * N)...,) + p1, p2 = p[1:n], p[(n + 1):(2N)] ip = invperm(p) - ip1, ip2 = ip[1:N], ip[N+1:2N] + ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] d = @constinferred TensorKit.permute(f1, f2, p1, p2) - @test dim(incoming) ≈ sum(abs2(coef)*dim(f1.coupled) for ((f1,f2), coef) in d) - d2 = Dict{typeof((f1,f2)), valtype(d)}() - for ((f1′,f2′), coeff) in d - d′ = TensorKit.permute(f1′,f2′, ip1, ip2) - for ((f1′′,f2′′), coeff2) in d′ - d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff2*coeff + @test dim(incoming) ≈ + sum(abs2(coef) * dim(f1.coupled) for ((f1, f2), coef) in d) + d2 = Dict{typeof((f1, f2)),valtype(d)}() + for ((f1′, f2′), coeff) in d + d′ = TensorKit.permute(f1′, f2′, ip1, ip2) + for ((f1′′, f2′′), coeff2) in d′ + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + + coeff2 * coeff end end - for ((f1′,f2′), coeff2) in d2 + for ((f1′, f2′), coeff2) in d2 if f1 == f1′ && f2 == f2′ @test coeff2 ≈ 1 else @@ -409,24 +416,24 @@ ti = time() Af2 = convert(Array, f2) sz1 = size(Af1) sz2 = size(Af2) - d1 = prod(sz1[1:end-1]) - d2 = prod(sz2[1:end-1]) + d1 = prod(sz1[1:(end - 1)]) + d2 = prod(sz2[1:(end - 1)]) dc = sz1[end] A = reshape(reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))', - (sz1[1:end-1]..., sz2[1:end-1]...)) - Ap = permutedims(A, (p1..., p2...)); + (sz1[1:(end - 1)]..., sz2[1:(end - 1)]...)) + Ap = permutedims(A, (p1..., p2...)) A2 = zero(Ap) - for ((f1′,f2′), coeff) in d + for ((f1′, f2′), coeff) in d Af1′ = convert(Array, f1′) Af2′ = convert(Array, f2′) sz1′ = size(Af1′) sz2′ = size(Af2′) - d1′ = prod(sz1′[1:end-1]) - d2′ = prod(sz2′[1:end-1]) + d1′ = prod(sz1′[1:(end - 1)]) + d2′ = prod(sz2′[1:(end - 1)]) dc′ = sz1′[end] - A2 += coeff*reshape(reshape(Af1′,(d1′,dc′)) * - reshape(Af2′,(d2′,dc′))', - (sz1′[1:end-1]..., sz2′[1:end-1]...)) + A2 += coeff * reshape(reshape(Af1′, (d1′, dc′)) * + reshape(Af2′, (d2′, dc′))', + (sz1′[1:(end - 1)]..., sz2′[1:(end - 1)]...)) end @test Ap ≈ A2 end @@ -434,25 +441,26 @@ ti = time() end end @testset "Double fusion tree $Istr: transposition" begin - for n = 0:2N - i0 = rand(1:2N) - p = mod1.(i0 .+ (1:2N), 2N) - ip = mod1.(-i0 .+ (1:2N), 2N) - p′ = tuple(getindex.(Ref(vcat(1:N, 2N:-1:N+1)), p)...) - p1, p2 = p′[1:n], p′[2N:-1:n+1] - ip′ = tuple(getindex.(Ref(vcat(1:n, 2N:-1:n+1)), ip)...) - ip1, ip2 = ip′[1:N], ip′[2N:-1:N+1] + for n in 0:(2N) + i0 = rand(1:(2N)) + p = mod1.(i0 .+ (1:(2N)), 2N) + ip = mod1.(-i0 .+ (1:(2N)), 2N) + p′ = tuple(getindex.(Ref(vcat(1:N, (2N):-1:(N + 1))), p)...) + p1, p2 = p′[1:n], p′[(2N):-1:(n + 1)] + ip′ = tuple(getindex.(Ref(vcat(1:n, (2N):-1:(n + 1))), ip)...) + ip1, ip2 = ip′[1:N], ip′[(2N):-1:(N + 1)] d = @constinferred transpose(f1, f2, p1, p2) - @test dim(incoming) ≈ sum(abs2(coef)*dim(f1.coupled) for ((f1,f2), coef) in d) - d2 = Dict{typeof((f1,f2)), valtype(d)}() - for ((f1′,f2′), coeff) in d - d′ = transpose(f1′,f2′, ip1, ip2) - for ((f1′′,f2′′), coeff2) in d′ - d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff2*coeff + @test dim(incoming) ≈ + sum(abs2(coef) * dim(f1.coupled) for ((f1, f2), coef) in d) + d2 = Dict{typeof((f1, f2)),valtype(d)}() + for ((f1′, f2′), coeff) in d + d′ = transpose(f1′, f2′, ip1, ip2) + for ((f1′′, f2′′), coeff2) in d′ + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff end end - for ((f1′,f2′), coeff2) in d2 + for ((f1′, f2′), coeff2) in d2 if f1 == f1′ && f2 == f2′ @test coeff2 ≈ 1 else @@ -462,10 +470,10 @@ ti = time() if BraidingStyle(I) isa Bosonic d3 = permute(f1, f2, p1, p2) - for (f1′,f2′) in union(keys(d), keys(d3)) - coeff1 = get(d, (f1′,f2′), zero(valtype(d))) - coeff3 = get(d3, (f1′,f2′), zero(valtype(d3))) - @test isapprox(coeff1, coeff3; atol = 1e-12) + for (f1′, f2′) in union(keys(d), keys(d3)) + coeff1 = get(d, (f1′, f2′), zero(valtype(d))) + coeff3 = get(d3, (f1′, f2′), zero(valtype(d3))) + @test isapprox(coeff1, coeff3; atol=1e-12) end end @@ -474,44 +482,45 @@ ti = time() Af2 = convert(Array, f2) sz1 = size(Af1) sz2 = size(Af2) - d1 = prod(sz1[1:end-1]) - d2 = prod(sz2[1:end-1]) + d1 = prod(sz1[1:(end - 1)]) + d2 = prod(sz2[1:(end - 1)]) dc = sz1[end] A = reshape(reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))', - (sz1[1:end-1]..., sz2[1:end-1]...)) - Ap = permutedims(A, (p1..., p2...)); + (sz1[1:(end - 1)]..., sz2[1:(end - 1)]...)) + Ap = permutedims(A, (p1..., p2...)) A2 = zero(Ap) - for ((f1′,f2′), coeff) in d + for ((f1′, f2′), coeff) in d Af1′ = convert(Array, f1′) Af2′ = convert(Array, f2′) sz1′ = size(Af1′) sz2′ = size(Af2′) - d1′ = prod(sz1′[1:end-1]) - d2′ = prod(sz2′[1:end-1]) + d1′ = prod(sz1′[1:(end - 1)]) + d2′ = prod(sz2′[1:(end - 1)]) dc′ = sz1′[end] - A2 += coeff*reshape(reshape(Af1′,(d1′,dc′)) * - reshape(Af2′,(d2′,dc′))', - (sz1′[1:end-1]..., sz2′[1:end-1]...)) + A2 += coeff * reshape(reshape(Af1′, (d1′, dc′)) * + reshape(Af2′, (d2′, dc′))', + (sz1′[1:(end - 1)]..., sz2′[1:(end - 1)]...)) end @test Ap ≈ A2 end end end @testset "Double fusion tree $Istr: planar trace" begin - d1 = transpose(f1, f1, (N+1, 1:N..., (2N:-1:N+3)...), (N+2,)) - f1front, = TK.split(f1, N-1) - T = typeof(Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1]) - d2 = Dict{typeof((f1front,f1front)), T}() + d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) + f1front, = TK.split(f1, N - 1) + T = typeof(Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1]) + d2 = Dict{typeof((f1front, f1front)),T}() for ((f1′, f2′), coeff′) in d1 - for ((f1′′,f2′′), coeff′′) in - TK.planar_trace(f1′, f2′, (2:N...,), (1, (2N:-1:N+3)...), (N+1,), (N+2,)) + for ((f1′′, f2′′), coeff′′) in + TK.planar_trace(f1′, f2′, (2:N...,), (1, ((2N):-1:(N + 3))...), (N + 1,), + (N + 2,)) coeff = coeff′ * coeff′′ - d2[(f1′′,f2′′)] = get(d2, (f1′′,f2′′), zero(coeff)) + coeff + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff end end - for ((f1_,f2_), coeff) in d2 - if (f1_,f2_) == (f1front, f1front) - @test coeff ≈ dim(f1.coupled)/dim(f1front.coupled) + for ((f1_, f2_), coeff) in d2 + if (f1_, f2_) == (f1front, f1front) + @test coeff ≈ dim(f1.coupled) / dim(f1front.coupled) else @test abs(coeff) < 1e-12 end @@ -520,6 +529,6 @@ ti = time() end tf = time() printstyled("Finished fusion tree tests in ", - string(round(tf-ti; sigdigits=3)), - " seconds."; bold = true, color = Base.info_color()) + string(round(tf - ti; sigdigits=3)), + " seconds."; bold=true, color=Base.info_color()) println() diff --git a/test/newsectors.jl b/test/newsectors.jl index 39cd6bfc..4aa5e606 100644 --- a/test/newsectors.jl +++ b/test/newsectors.jl @@ -11,7 +11,7 @@ struct NewSU2Irrep <: TensorKit.Sector j::HalfInt function NewSU2Irrep(j) j >= zero(j) || error("Not a valid SU₂ irrep") - new(j) + return new(j) end end Base.convert(::Type{NewSU2Irrep}, j::Real) = NewSU2Irrep(j) @@ -19,11 +19,12 @@ Base.convert(::Type{NewSU2Irrep}, j::Real) = NewSU2Irrep(j) const _su2one = NewSU2Irrep(zero(HalfInt)) Base.one(::Type{NewSU2Irrep}) = _su2one Base.conj(s::NewSU2Irrep) = s -TensorKit.:⊗(s1::NewSU2Irrep, s2::NewSU2Irrep) = - TensorKit.SectorSet{NewSU2Irrep}(abs(s1.j-s2.j):(s1.j+s2.j)) +function TensorKit.:⊗(s1::NewSU2Irrep, s2::NewSU2Irrep) + return TensorKit.SectorSet{NewSU2Irrep}(abs(s1.j - s2.j):(s1.j + s2.j)) +end Base.IteratorSize(::Type{TensorKit.SectorValues{NewSU2Irrep}}) = Base.IsInfinite() -Base.iterate(::TensorKit.SectorValues{NewSU2Irrep}, i = 0) = (NewSU2Irrep(half(i)), i+1) +Base.iterate(::TensorKit.SectorValues{NewSU2Irrep}, i=0) = (NewSU2Irrep(half(i)), i + 1) # Base.getindex(::SectorValues{NewSU2Irrep}, i::Int) = # 1 <= i ? NewSU2Irrep(half(i-1)) : throw(BoundsError(values(NewSU2Irrep), i)) # findindex(::SectorValues{NewSU2Irrep}, s::NewSU2Irrep) = twice(s.j)+1 @@ -34,32 +35,33 @@ TensorKit.FusionStyle(::Type{NewSU2Irrep}) = GenericFusion() TensorKit.BraidingStyle(::Type{NewSU2Irrep}) = Bosonic() Base.isreal(::Type{NewSU2Irrep}) = true -TensorKit.Nsymbol(sa::NewSU2Irrep, sb::NewSU2Irrep, sc::NewSU2Irrep) = - convert(Int, WignerSymbols.δ(sa.j, sb.j, sc.j)) +function TensorKit.Nsymbol(sa::NewSU2Irrep, sb::NewSU2Irrep, sc::NewSU2Irrep) + return convert(Int, WignerSymbols.δ(sa.j, sb.j, sc.j)) +end function TensorKit.Fsymbol(s1::NewSU2Irrep, s2::NewSU2Irrep, s3::NewSU2Irrep, - s4::NewSU2Irrep, s5::NewSU2Irrep, s6::NewSU2Irrep) + s4::NewSU2Irrep, s5::NewSU2Irrep, s6::NewSU2Irrep) n1 = Nsymbol(s1, s2, s5) n2 = Nsymbol(s5, s3, s4) n3 = Nsymbol(s2, s3, s6) n4 = Nsymbol(s1, s6, s4) f = all(==(_su2one), (s1, s2, s3, s4, s5, s6)) ? 1.0 : - sqrt(dim(s5) * dim(s6)) * WignerSymbols.racahW(Float64, s1.j, s2.j, - s4.j, s3.j, s5.j, s6.j) - return fill(f , (n1, n2, n3, n4)) + sqrt(dim(s5) * dim(s6)) * WignerSymbols.racahW(Float64, s1.j, s2.j, + s4.j, s3.j, s5.j, s6.j) + return fill(f, (n1, n2, n3, n4)) end function TensorKit.Rsymbol(sa::NewSU2Irrep, sb::NewSU2Irrep, sc::NewSU2Irrep) - Nsymbol(sa, sb, sc) > 0 || return fill(0., (0,0)) - return fill(iseven(convert(Int, sa.j+sb.j-sc.j)) ? 1.0 : -1.0, (1, 1)) + Nsymbol(sa, sb, sc) > 0 || return fill(0.0, (0, 0)) + return fill(iseven(convert(Int, sa.j + sb.j - sc.j)) ? 1.0 : -1.0, (1, 1)) end TensorKit.dim(s::NewSU2Irrep) = twice(s.j) + 1 - function TensorKit.fusiontensor(a::NewSU2Irrep, b::NewSU2Irrep, c::NewSU2Irrep) C = Array{Float64}(undef, dim(a), dim(b), dim(c), 1) ja, jb, jc = a.j, b.j, c.j - for kc = 1:dim(c), kb = 1:dim(b), ka = 1:dim(a) - C[ka,kb,kc,1] = WignerSymbols.clebschgordan(ja, ja+1-ka, jb, jb+1-kb, jc, jc+1-kc) + for kc in 1:dim(c), kb in 1:dim(b), ka in 1:dim(a) + C[ka, kb, kc, 1] = WignerSymbols.clebschgordan(ja, ja + 1 - ka, jb, jb + 1 - kb, jc, + jc + 1 - kc) end return C end diff --git a/test/runtests.jl b/test/runtests.jl index 23d2ce8f..e4314de8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ using TensorOperations using Base.Iterators: take, product using SUNRepresentations: SUNIrrep const SU3Irrep = SUNIrrep{3} -import LinearAlgebra +using LinearAlgebra: LinearAlgebra include("newsectors.jl") using .NewSectors @@ -21,12 +21,12 @@ smallset(::Type{I}) where {I<:Sector} = take(values(I), 5) smallset(::Type{FermionNumber}) = FermionNumber.((0, +1, -1, +2, -2)) function smallset(::Type{ProductSector{Tuple{I1,I2}}}) where {I1,I2} iter = product(smallset(I1), smallset(I2)) - s = collect(i ⊠ j for (i,j) in iter if dim(i)*dim(j) <= 6) + s = collect(i ⊠ j for (i, j) in iter if dim(i) * dim(j) <= 6) return length(s) > 6 ? rand(s, 6) : s end function smallset(::Type{ProductSector{Tuple{I1,I2,I3}}}) where {I1,I2,I3} iter = product(smallset(I1), smallset(I2), smallset(I3)) - s = collect(i ⊠ j ⊠ k for (i,j,k) in iter if dim(i)*dim(j)*dim(k) <= 6) + s = collect(i ⊠ j ⊠ k for (i, j, k) in iter if dim(i) * dim(j) * dim(k) <= 6) return length(s) > 6 ? rand(s, 6) : s end function randsector(::Type{I}) where {I<:Sector} @@ -63,6 +63,6 @@ include("spaces.jl") include("tensors.jl") Tf = time() printstyled("Finished all tests in ", - string(round((Tf-Ti)/60; sigdigits=3)), - " minutes."; bold = true, color = Base.info_color()) + string(round((Tf - Ti) / 60; sigdigits=3)), + " minutes."; bold=true, color=Base.info_color()) println() diff --git a/test/spaces.jl b/test/spaces.jl index 22b7e7ba..3a56fe60 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -58,7 +58,8 @@ end @test !isdual(V') @test V == CartesianSpace(Trivial() => d) == CartesianSpace(Dict(Trivial() => d)) @test @constinferred(hash(V)) == hash(deepcopy(V)) - @test V == @constinferred(dual(V)) == @constinferred(conj(V)) == @constinferred(adjoint(V)) + @test V == @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) @test field(V) == ℝ @test @constinferred(sectortype(V)) == Trivial @test ((@constinferred sectors(V))...,) == (Trivial(),) @@ -71,17 +72,17 @@ end @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) W = @constinferred ℝ^1 @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) - @test @constinferred(⊕(V,V)) == ℝ^(2d) - @test @constinferred(⊕(V,oneunit(V))) == ℝ^(d+1) - @test @constinferred(⊕(V,V,V,V)) == ℝ^(4d) - @test @constinferred(fuse(V,V)) == ℝ^(d^2) - @test @constinferred(fuse(V,V',V,V')) == ℝ^(d^4) + @test @constinferred(⊕(V, V)) == ℝ^(2d) + @test @constinferred(⊕(V, oneunit(V))) == ℝ^(d + 1) + @test @constinferred(⊕(V, V, V, V)) == ℝ^(4d) + @test @constinferred(fuse(V, V)) == ℝ^(d^2) + @test @constinferred(fuse(V, V', V, V')) == ℝ^(d^4) @test @constinferred(flip(V)) == V' @test flip(V) ≅ V @test flip(V) ≾ V @test flip(V) ≿ V - @test V ≺ ⊕(V,V) - @test !(V ≻ ⊕(V,V)) + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) @test @constinferred(infimum(V, ℝ^3)) == V @test @constinferred(supremum(V', ℝ^3)) == ℝ^3 end @@ -101,7 +102,8 @@ end @test isdual(V') @test V == ComplexSpace(Trivial() => d) == ComplexSpace(Dict(Trivial() => d)) @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') - @test @constinferred(dual(V)) == @constinferred(conj(V)) == @constinferred(adjoint(V)) != V + @test @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) != V @test @constinferred(field(V)) == ℂ @test @constinferred(sectortype(V)) == Trivial @test @constinferred(sectortype(V)) == Trivial @@ -112,24 +114,24 @@ end @test dim(@constinferred(typeof(V)())) == 0 @test (sectors(typeof(V)())...,) == () @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) - @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial()=>d) == ℂ[](d) == typeof(V)(d) + @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) W = @constinferred ℂ^1 @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) @test @constinferred(⊕(V, V)) == ℂ^(2d) @test_throws SpaceMismatch (⊕(V, V')) @test_throws MethodError (⊕(ℝ^d, ℂ^d)) @test_throws MethodError (⊗(ℝ^d, ℂ^d)) - @test @constinferred(⊕(V,V)) == ℂ^(2d) - @test @constinferred(⊕(V,oneunit(V))) == ℂ^(d+1) - @test @constinferred(⊕(V,V,V,V)) == ℂ^(4d) - @test @constinferred(fuse(V,V)) == ℂ^(d^2) - @test @constinferred(fuse(V,V',V,V')) == ℂ^(d^4) + @test @constinferred(⊕(V, V)) == ℂ^(2d) + @test @constinferred(⊕(V, oneunit(V))) == ℂ^(d + 1) + @test @constinferred(⊕(V, V, V, V)) == ℂ^(4d) + @test @constinferred(fuse(V, V)) == ℂ^(d^2) + @test @constinferred(fuse(V, V', V, V')) == ℂ^(d^4) @test @constinferred(flip(V)) == V' @test flip(V) ≅ V @test flip(V) ≾ V @test flip(V) ≿ V - @test V ≺ ⊕(V,V) - @test !(V ≻ ⊕(V,V)) + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) @test @constinferred(infimum(V, ℂ^3)) == V @test @constinferred(supremum(V', (ℂ^3)')) == dual(ℂ^3) == conj(ℂ^3) end @@ -164,32 +166,32 @@ end @timedtestset "ElementarySpace: $(TensorKit.type_repr(Vect[I]))" for I in sectorlist if Base.IteratorSize(values(I)) === Base.IsInfinite() - set = unique(vcat(one(I), [randsector(I) for k = 1:10])) - gen = (c=>2 for c in set) + set = unique(vcat(one(I), [randsector(I) for k in 1:10])) + gen = (c => 2 for c in set) else - gen = (values(I)[k]=>(k+1) for k in 1:length(values(I))) + gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) end V = GradedSpace(gen) @test eval(Meta.parse(TensorKit.type_repr(typeof(V)))) == typeof(V) @test eval(Meta.parse(sprint(show, V))) == V @test eval(Meta.parse(sprint(show, V'))) == V' - @test V' == GradedSpace(gen; dual = true) + @test V' == GradedSpace(gen; dual=true) @test V == @constinferred GradedSpace(gen...) - @test V' == @constinferred GradedSpace(gen...; dual = true) + @test V' == @constinferred GradedSpace(gen...; dual=true) @test V == @constinferred GradedSpace(tuple(gen...)) - @test V' == @constinferred GradedSpace(tuple(gen...); dual = true) + @test V' == @constinferred GradedSpace(tuple(gen...); dual=true) @test V == @constinferred GradedSpace(Dict(gen)) - @test V' == @constinferred GradedSpace(Dict(gen); dual = true) + @test V' == @constinferred GradedSpace(Dict(gen); dual=true) @test V == @inferred Vect[I](gen) - @test V' == @constinferred Vect[I](gen; dual = true) + @test V' == @constinferred Vect[I](gen; dual=true) @test V == @constinferred Vect[I](gen...) - @test V' == @constinferred Vect[I](gen...; dual = true) + @test V' == @constinferred Vect[I](gen...; dual=true) @test V == @constinferred Vect[I](Dict(gen)) - @test V' == @constinferred Vect[I](Dict(gen); dual = true) - @test V == @constinferred typeof(V)(c=>dim(V,c) for c in sectors(V)) + @test V' == @constinferred Vect[I](Dict(gen); dual=true) + @test V == @constinferred typeof(V)(c => dim(V, c) for c in sectors(V)) if I isa ZNIrrep @test V == @constinferred typeof(V)(V.dims) - @test V' == @constinferred typeof(V)(V.dims; dual = true) + @test V' == @constinferred typeof(V)(V.dims; dual=true) end @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') @test V == GradedSpace(reverse(collect(gen))...) @@ -198,11 +200,11 @@ end # space with no sectors @test dim(@constinferred(typeof(V)())) == 0 # space with a single sector - W = @constinferred GradedSpace(one(I)=>1) - @test W == GradedSpace(one(I)=>1, randsector(I) => 0) + W = @constinferred GradedSpace(one(I) => 1) + @test W == GradedSpace(one(I) => 1, randsector(I) => 0) @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError GradedSpace(one(I)=>1, randsector(I) => 0, one(I)=>3) + @test_throws ArgumentError GradedSpace(one(I) => 1, randsector(I) => 0, one(I) => 3) @test eval(Meta.parse(sprint(show, W))) == W @test isa(V, VectorSpace) @test isa(V, ElementarySpace) @@ -210,38 +212,39 @@ end @test isa(InnerProductStyle(V), EuclideanProduct) @test isa(V, GradedSpace) @test isa(V, GradedSpace{I}) - @test @constinferred(dual(V)) == @constinferred(conj(V)) == @constinferred(adjoint(V)) != V + @test @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) != V @test @constinferred(field(V)) == ℂ @test @constinferred(sectortype(V)) == I slist = @constinferred sectors(V) @test @constinferred(TensorKit.hassector(V, first(slist))) - @test @constinferred(dim(V)) == sum(dim(s)*dim(V, s) for s in slist) + @test @constinferred(dim(V)) == sum(dim(s) * dim(V, s) for s in slist) @constinferred dim(V, first(slist)) if hasfusiontensor(I) @test @constinferred(TensorKit.axes(V)) == Base.OneTo(dim(V)) end - @test @constinferred(⊕(V,V)) == Vect[I](c=>2dim(V,c) for c in sectors(V)) - @test @constinferred(⊕(V,V,V,V)) == Vect[I](c=>4dim(V,c) for c in sectors(V)) - @test @constinferred(⊕(V,oneunit(V))) == - Vect[I](c=>isone(c)+dim(V,c) for c in sectors(V)) - @test @constinferred(fuse(V,oneunit(V))) == V + @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) + @test @constinferred(⊕(V, V, V, V)) == Vect[I](c => 4dim(V, c) for c in sectors(V)) + @test @constinferred(⊕(V, oneunit(V))) == + Vect[I](c => isone(c) + dim(V, c) for c in sectors(V)) + @test @constinferred(fuse(V, oneunit(V))) == V d = Dict{I,Int}() for a in sectors(V), b in sectors(V) for c in a ⊗ b - d[c] = get(d, c, 0) + dim(V, a)*dim(V, b)*Nsymbol(a,b,c) + d[c] = get(d, c, 0) + dim(V, a) * dim(V, b) * Nsymbol(a, b, c) end end - @test @constinferred(fuse(V,V)) == GradedSpace(d) + @test @constinferred(fuse(V, V)) == GradedSpace(d) @test @constinferred(flip(V)) == - Vect[I](conj(c)=>dim(V,c) for c in sectors(V))' + Vect[I](conj(c) => dim(V, c) for c in sectors(V))' @test flip(V) ≅ V @test flip(V) ≾ V @test flip(V) ≿ V @test @constinferred(⊕(V, V)) == @constinferred supremum(V, ⊕(V, V)) @test V == @constinferred infimum(V, ⊕(V, V)) - @test V ≺ ⊕(V,V) - @test !(V ≻ ⊕(V,V)) - @test infimum(V, GradedSpace(one(I)=>3)) == GradedSpace(one(I)=>2) + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) + @test infimum(V, GradedSpace(one(I) => 3)) == GradedSpace(one(I) => 2) @test_throws SpaceMismatch (⊕(V, V')) end @@ -257,15 +260,15 @@ end @test @constinferred(hash(P)) == hash(deepcopy(P)) != hash(P') @test P == deepcopy(P) @test P == typeof(P)(P...) - @constinferred (x->tuple(x...))(P) + @constinferred (x -> tuple(x...))(P) @test @constinferred(dual(P)) == P' @test @constinferred(field(P)) == ℂ @test @constinferred(*(V1, V2, V3, V4)) == P @test @constinferred(⊗(V1, V2, V3, V4)) == P - @test @constinferred(⊗(V1, V2⊗V3⊗V4)) == P - @test @constinferred(⊗(V1⊗V2, V3⊗V4)) == P - @test @constinferred(⊗(V1, V2, V3⊗V4)) == P - @test @constinferred(⊗(V1, V2⊗V3, V4)) == P + @test @constinferred(⊗(V1, V2 ⊗ V3 ⊗ V4)) == P + @test @constinferred(⊗(V1 ⊗ V2, V3 ⊗ V4)) == P + @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P + @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P @test @constinferred(insertunit(P, 3)) == V1 * V2 * oneunit(V1) * V3 * V4 @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 @@ -275,8 +278,8 @@ end @test @constinferred(⊗(V1)) == ProductSpace(V1) @test eval(Meta.parse(sprint(show, ⊗(V1)))) == ⊗(V1) @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == - @constinferred(one(P)) == @constinferred(one(typeof(P))) == - ProductSpace{ComplexSpace}(()) + @constinferred(one(P)) == @constinferred(one(typeof(P))) == + ProductSpace{ComplexSpace}(()) @test eval(Meta.parse(sprint(show, one(P)))) == one(P) @test @constinferred(⊗(one(P), P)) == P @test @constinferred(⊗(P, one(P))) == P @@ -286,7 +289,7 @@ end @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3, V4)) @test @constinferred(dim(P, 2)) == dim(V2) @test @constinferred(sectors(P)) == - (mapreduce(sectors, (a, b)->tuple(a..., b...), (V1, V2, V3, V4)),) + (mapreduce(sectors, (a, b) -> tuple(a..., b...), (V1, V2, V3, V4)),) cube(x) = x^3 @test @constinferred(cube(V1)) == V1 ⊗ V1 ⊗ V1 N = 3 @@ -297,16 +300,17 @@ end @test (blocksectors(P ⊗ ℂ^0)...,) == () @test @constinferred(blockdim(P, first(blocksectors(P)))) == dim(P) @test Base.IteratorEltype(P) == Base.IteratorEltype(typeof(P)) == - Base.IteratorEltype(P.spaces) + Base.IteratorEltype(P.spaces) @test Base.IteratorSize(P) == Base.IteratorSize(typeof(P)) == - Base.IteratorSize(P.spaces) + Base.IteratorSize(P.spaces) @test Base.eltype(P) == Base.eltype(typeof(P)) == typeof(V1) @test eltype(collect(P)) == typeof(V1) @test collect(P) == [V1, V2, V3, V4] end @timedtestset "ProductSpace{SU₂Space}" begin - V1, V2, V3 = SU₂Space(0=>3, 1//2=>1), SU₂Space(0=>2, 1=>1), SU₂Space(1//2=>1, 1=>1)' + V1, V2, V3 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), + SU₂Space(1 // 2 => 1, 1 => 1)' P = @constinferred ProductSpace(V1, V2, V3) @test eval(Meta.parse(sprint(show, P))) == P @test eval(Meta.parse(sprint(show, typeof(P)))) == typeof(P) @@ -320,7 +324,7 @@ end @test @constinferred(*(V1, V2, V3)) == P @test @constinferred(⊗(V1, V2, V3)) == P @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' - @test @constinferred(insertunit(P, 3; conj = true)) == V1 * V2 * oneunit(V1)' * V3 + @test @constinferred(insertunit(P, 3; conj=true)) == V1 * V2 * oneunit(V1)' * V3 @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 @@ -329,21 +333,21 @@ end @test fuse(flip(V1) ⊗ V2) ⊗ flip(V3) ≅ V1 ⊗ V2 ⊗ V3 @test @constinferred(⊗(V1)) == ProductSpace(V1) @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == - @constinferred(one(P)) == @constinferred(one(typeof(P))) == - ProductSpace{ComplexSpace}(()) + @constinferred(one(P)) == @constinferred(one(typeof(P))) == + ProductSpace{ComplexSpace}(()) @test @constinferred(dims(P)) == map(dim, (V1, V2, V3)) @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3)) for s in @constinferred(sectors(P)) @test hassector(P, s) @test @constinferred(dims(P, s)) == dim.((V1, V2, V3), s) end - @test sum(dim(c)*blockdim(P, c) for c in @constinferred(blocksectors(P))) == dim(P) + @test sum(dim(c) * blockdim(P, c) for c in @constinferred(blocksectors(P))) == dim(P) end @timedtestset "Deligne tensor product of spaces" begin - V1 = SU₂Space(0=>3, 1//2=>1) - V2 = SU₂Space(0=>2, 1=>1)' - V3 = ℤ₃Space(0=>3, 1=>2, 2=>1) + V1 = SU₂Space(0 => 3, 1 // 2 => 1) + V2 = SU₂Space(0 => 2, 1 => 1)' + V3 = ℤ₃Space(0 => 3, 1 => 2, 2 => 1) V4 = ℂ^3 for W1 in (V1, V2, V3, V4) @@ -352,17 +356,17 @@ end for W4 in (V1, V2, V3, V4) Ws = @constinferred(W1 ⊠ W2 ⊠ W3 ⊠ W4) @test Ws == @constinferred((W1 ⊠ W2) ⊠ (W3 ⊠ W4)) == - @constinferred(((W1 ⊠ W2) ⊠ W3) ⊠ W4) == - @constinferred((W1 ⊠ (W2 ⊠ W3)) ⊠ W4) == - @constinferred(W1 ⊠ ((W2 ⊠ W3)) ⊠ W4) == - @constinferred(W1 ⊠ (W2 ⊠ (W3 ⊠ W4))) + @constinferred(((W1 ⊠ W2) ⊠ W3) ⊠ W4) == + @constinferred((W1 ⊠ (W2 ⊠ W3)) ⊠ W4) == + @constinferred(W1 ⊠ ((W2 ⊠ W3)) ⊠ W4) == + @constinferred(W1 ⊠ (W2 ⊠ (W3 ⊠ W4))) I1, I2, I3, I4 = map(sectortype, (W1, W2, W3, W4)) I = sectortype(Ws) @test I == @constinferred((I1 ⊠ I2) ⊠ (I3 ⊠ I4)) == - @constinferred(((I1 ⊠ I2) ⊠ I3) ⊠ I4) == - @constinferred((I1 ⊠ (I2 ⊠ I3)) ⊠ I4) == - @constinferred(I1 ⊠ ((I2 ⊠ I3)) ⊠ I4) == - @constinferred(I1 ⊠ (I2 ⊠ (I3 ⊠ I4))) + @constinferred(((I1 ⊠ I2) ⊠ I3) ⊠ I4) == + @constinferred((I1 ⊠ (I2 ⊠ I3)) ⊠ I4) == + @constinferred(I1 ⊠ ((I2 ⊠ I3)) ⊠ I4) == + @constinferred(I1 ⊠ (I2 ⊠ (I3 ⊠ I4))) @test dim(Ws) == dim(W1) * dim(W2) * dim(W3) * dim(W4) end end @@ -372,14 +376,14 @@ end @test dim((V1 ⊗ V2) ⊠ V3) == dim(V1) * dim(V2) * dim(V3) @test sectortype((V1 ⊗ V2) ⊠ V3 ⊠ V4) == Irrep[SU₂ × ℤ₃] @test dim((V1 ⊗ V2) ⊠ V3 ⊠ V4) == dim(V1) * dim(V2) * dim(V3) * dim(V4) - @test fuse(V2 ⊠ V4) == fuse(V4 ⊠ V2) == SU₂Space(0=>6, 1=>3) - @test fuse(V3 ⊠ V4) == fuse(V4 ⊠ V3) == ℤ₃Space(0=>9, 1=>6, 2=>3) + @test fuse(V2 ⊠ V4) == fuse(V4 ⊠ V2) == SU₂Space(0 => 6, 1 => 3) + @test fuse(V3 ⊠ V4) == fuse(V4 ⊠ V3) == ℤ₃Space(0 => 9, 1 => 6, 2 => 3) end @timedtestset "HomSpace" begin - V1, V2, V3, V4, V5 = SU₂Space(0=>3, 1//2=>1), SU₂Space(0=>2, 1=>1), - SU₂Space(1//2=>1, 1=>1)', SU₂Space(0=>2, 1//2=>2), - SU₂Space(0=>1, 1//2=>1, 3//2=>1)' + V1, V2, V3, V4, V5 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), + SU₂Space(1 // 2 => 1, 1 => 1)', SU₂Space(0 => 2, 1 // 2 => 2), + SU₂Space(0 => 1, 1 // 2 => 1, 3 // 2 => 1)' W = TensorKit.HomSpace(V1 ⊗ V2, V3 ⊗ V4 ⊗ V5) @test W == (V3 ⊗ V4 ⊗ V5 → V1 ⊗ V2) @test W == (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) @@ -399,6 +403,6 @@ end tf = time() printstyled("Finished vector space tests in ", - string(round(tf-ti; sigdigits=3)), - " seconds."; bold = true, color = Base.info_color()) + string(round(tf - ti; sigdigits=3)), + " seconds."; bold=true, color=Base.info_color()) println() diff --git a/test/tensors.jl b/test/tensors.jl index 882813f7..951dcd22 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -1,53 +1,53 @@ Vtr = (ℂ^3, - (ℂ^4)', - ℂ^5, - ℂ^6, - (ℂ^7)') -Vℤ₂ = (ℂ[Z2Irrep](0=>1, 1=>1), - ℂ[Z2Irrep](0=>1, 1=>2)', - ℂ[Z2Irrep](0=>3, 1=>2)', - ℂ[Z2Irrep](0=>2, 1=>3), - ℂ[Z2Irrep](0=>2, 1=>5)) -Vfℤ₂ = (ℂ[FermionParity](0=>1, 1=>1), - ℂ[FermionParity](0=>1, 1=>2)', - ℂ[FermionParity](0=>3, 1=>2)', - ℂ[FermionParity](0=>2, 1=>3), - ℂ[FermionParity](0=>2, 1=>5)) -Vℤ₃ = (ℂ[Z3Irrep](0=>1, 1=>2, 2=>2), - ℂ[Z3Irrep](0=>3, 1=>1, 2=>1), - ℂ[Z3Irrep](0=>2, 1=>2, 2=>1)', - ℂ[Z3Irrep](0=>1, 1=>2, 2=>3), - ℂ[Z3Irrep](0=>1, 1=>3, 2=>3)') -VU₁ = (ℂ[U1Irrep](0=>1, 1=>2, -1=>2), - ℂ[U1Irrep](0=>3, 1=>1, -1=>1), - ℂ[U1Irrep](0=>2, 1=>2, -1=>1)', - ℂ[U1Irrep](0=>1, 1=>2, -1=>3), - ℂ[U1Irrep](0=>1, 1=>3, -1=>3)') -VfU₁ = (ℂ[FermionNumber](0=>1, 1=>2, -1=>2), - ℂ[FermionNumber](0=>3, 1=>1, -1=>1), - ℂ[FermionNumber](0=>2, 1=>2, -1=>1)', - ℂ[FermionNumber](0=>1, 1=>2, -1=>3), - ℂ[FermionNumber](0=>1, 1=>3, -1=>3)') -VCU₁ = (ℂ[CU1Irrep]((0,0)=>1, (0,1)=>2, 1=>1), - ℂ[CU1Irrep]((0,0)=>3, (0,1)=>0, 1=>1), - ℂ[CU1Irrep]((0,0)=>1, (0,1)=>0, 1=>2)', - ℂ[CU1Irrep]((0,0)=>2, (0,1)=>2, 1=>1), - ℂ[CU1Irrep]((0,0)=>2, (0,1)=>1, 1=>2)') -VSU₂ = (ℂ[SU2Irrep](0=>3, 1//2=>1), - ℂ[SU2Irrep](0=>2, 1=>1), - ℂ[SU2Irrep](1//2=>1, 1=>1)', - ℂ[SU2Irrep](0=>2, 1//2=>2), - ℂ[SU2Irrep](0=>1, 1//2=>1, 3//2=>1)') -VfSU₂ = (ℂ[FermionSpin](0=>3, 1//2=>1), - ℂ[FermionSpin](0=>2, 1=>1), - ℂ[FermionSpin](1//2=>1, 1=>1)', - ℂ[FermionSpin](0=>2, 1//2=>2), - ℂ[FermionSpin](0=>1, 1//2=>1, 3//2=>1)') -VSU₃ = (ℂ[SU3Irrep]((0,0,0)=>3, (1,0,0)=>1), - ℂ[SU3Irrep]((0,0,0)=>3, (2,0,0)=>1)', - ℂ[SU3Irrep]((1,1,0)=>1, (2,1,0)=>1), - ℂ[SU3Irrep]((1,0,0)=>1, (2,0,0)=>1), - ℂ[SU3Irrep]((0,0,0)=>1, (1,0,0)=>1, (1,1,0)=>1)') + (ℂ^4)', + ℂ^5, + ℂ^6, + (ℂ^7)') +Vℤ₂ = (ℂ[Z2Irrep](0 => 1, 1 => 1), + ℂ[Z2Irrep](0 => 1, 1 => 2)', + ℂ[Z2Irrep](0 => 3, 1 => 2)', + ℂ[Z2Irrep](0 => 2, 1 => 3), + ℂ[Z2Irrep](0 => 2, 1 => 5)) +Vfℤ₂ = (ℂ[FermionParity](0 => 1, 1 => 1), + ℂ[FermionParity](0 => 1, 1 => 2)', + ℂ[FermionParity](0 => 3, 1 => 2)', + ℂ[FermionParity](0 => 2, 1 => 3), + ℂ[FermionParity](0 => 2, 1 => 5)) +Vℤ₃ = (ℂ[Z3Irrep](0 => 1, 1 => 2, 2 => 2), + ℂ[Z3Irrep](0 => 3, 1 => 1, 2 => 1), + ℂ[Z3Irrep](0 => 2, 1 => 2, 2 => 1)', + ℂ[Z3Irrep](0 => 1, 1 => 2, 2 => 3), + ℂ[Z3Irrep](0 => 1, 1 => 3, 2 => 3)') +VU₁ = (ℂ[U1Irrep](0 => 1, 1 => 2, -1 => 2), + ℂ[U1Irrep](0 => 3, 1 => 1, -1 => 1), + ℂ[U1Irrep](0 => 2, 1 => 2, -1 => 1)', + ℂ[U1Irrep](0 => 1, 1 => 2, -1 => 3), + ℂ[U1Irrep](0 => 1, 1 => 3, -1 => 3)') +VfU₁ = (ℂ[FermionNumber](0 => 1, 1 => 2, -1 => 2), + ℂ[FermionNumber](0 => 3, 1 => 1, -1 => 1), + ℂ[FermionNumber](0 => 2, 1 => 2, -1 => 1)', + ℂ[FermionNumber](0 => 1, 1 => 2, -1 => 3), + ℂ[FermionNumber](0 => 1, 1 => 3, -1 => 3)') +VCU₁ = (ℂ[CU1Irrep]((0, 0) => 1, (0, 1) => 2, 1 => 1), + ℂ[CU1Irrep]((0, 0) => 3, (0, 1) => 0, 1 => 1), + ℂ[CU1Irrep]((0, 0) => 1, (0, 1) => 0, 1 => 2)', + ℂ[CU1Irrep]((0, 0) => 2, (0, 1) => 2, 1 => 1), + ℂ[CU1Irrep]((0, 0) => 2, (0, 1) => 1, 1 => 2)') +VSU₂ = (ℂ[SU2Irrep](0 => 3, 1 // 2 => 1), + ℂ[SU2Irrep](0 => 2, 1 => 1), + ℂ[SU2Irrep](1 // 2 => 1, 1 => 1)', + ℂ[SU2Irrep](0 => 2, 1 // 2 => 2), + ℂ[SU2Irrep](0 => 1, 1 // 2 => 1, 3 // 2 => 1)') +VfSU₂ = (ℂ[FermionSpin](0 => 3, 1 // 2 => 1), + ℂ[FermionSpin](0 => 2, 1 => 1), + ℂ[FermionSpin](1 // 2 => 1, 1 => 1)', + ℂ[FermionSpin](0 => 2, 1 // 2 => 2), + ℂ[FermionSpin](0 => 1, 1 // 2 => 1, 3 // 2 => 1)') +VSU₃ = (ℂ[SU3Irrep]((0, 0, 0) => 3, (1, 0, 0) => 1), + ℂ[SU3Irrep]((0, 0, 0) => 3, (2, 0, 0) => 1)', + ℂ[SU3Irrep]((1, 1, 0) => 1, (2, 1, 0) => 1), + ℂ[SU3Irrep]((1, 0, 0) => 1, (2, 0, 0) => 1), + ℂ[SU3Irrep]((0, 0, 0) => 1, (1, 0, 0) => 1, (1, 1, 0) => 1)') for V in (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₃) V1, V2, V3, V4, V5 = V @@ -106,7 +106,7 @@ for V in spacelist W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Int, Float32, ComplexF64) if T == Int - t = TensorMap(sz->rand(-20:20, sz), W) + t = TensorMap(sz -> rand(-20:20, sz), W) else t = TensorMap(randn, T, W) end @@ -126,33 +126,32 @@ for V in spacelist @test codomain(t) == codomain(W) @test domain(t) == domain(W) @test isa(@constinferred(norm(t)), real(T)) - @test norm(t)^2 ≈ dot(t,t) + @test norm(t)^2 ≈ dot(t, t) α = rand(T) - @test norm(α*t) ≈ abs(α)*norm(t) - @test norm(t+t, 2) ≈ 2*norm(t, 2) - @test norm(t+t, 1) ≈ 2*norm(t, 1) - @test norm(t+t, Inf) ≈ 2*norm(t, Inf) - p = 3*rand(Float64) - @test norm(t+t, p) ≈ 2*norm(t, p) + @test norm(α * t) ≈ abs(α) * norm(t) + @test norm(t + t, 2) ≈ 2 * norm(t, 2) + @test norm(t + t, 1) ≈ 2 * norm(t, 1) + @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) + p = 3 * rand(Float64) + @test norm(t + t, p) ≈ 2 * norm(t, p) @test norm(t) ≈ norm(t') t2 = TensorMap(rand, T, W) β = rand(T) - @test @constinferred(dot(β*t2,α*t)) ≈ conj(β)*α*conj(dot(t,t2)) - @test dot(t2,t) ≈ conj(dot(t, t2)) - @test dot(t2,t) ≈ conj(dot(t2', t')) - @test dot(t2,t) ≈ dot(t', t2') + @test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t2', t')) + @test dot(t2, t) ≈ dot(t', t2') i1 = @constinferred(isomorphism(Matrix{T}, V1 ⊗ V2, V2 ⊗ V1)) i2 = @constinferred(isomorphism(Matrix{T}, V2 ⊗ V1, V1 ⊗ V2)) @test i1 * i2 == @constinferred(id(Matrix{T}, V1 ⊗ V2)) @test i2 * i1 == @constinferred(id(Matrix{T}, V2 ⊗ V1)) - w = @constinferred(isometry(Matrix{T}, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), V1)) - @test dim(w) == 2*dim(V1←V1) - @test w'*w == id(Matrix{T}, V1) - @test w*w' == (w*w')^2 + @test dim(w) == 2 * dim(V1 ← V1) + @test w' * w == id(Matrix{T}, V1) + @test w * w' == (w * w')^2 end end if hasfusiontensor(I) @@ -161,11 +160,11 @@ for V in spacelist for T in (Float32, ComplexF64) t = TensorMap(rand, T, W) t2 = TensorMap(rand, T, W) - @test norm(t, 2) ≈ norm(convert(Array,t), 2) - @test dot(t2,t) ≈ dot(convert(Array,t2), convert(Array, t)) + @test norm(t, 2) ≈ norm(convert(Array, t), 2) + @test dot(t2, t) ≈ dot(convert(Array, t2), convert(Array, t)) α = rand(T) - @test convert(Array, α*t) ≈ α*convert(Array,t) - @test convert(Array, t+t) ≈ 2*convert(Array,t) + @test convert(Array, α * t) ≈ α * convert(Array, t) + @test convert(Array, t + t) ≈ 2 * convert(Array, t) end end @timedtestset "Real and imaginary parts" begin @@ -190,14 +189,14 @@ for V in spacelist W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = Tensor(rand, ComplexF64, W) t′ = Tensor(rand, ComplexF64, W) - for k = 0:5 + for k in 0:5 for p in permutations(1:5) - p1 = ntuple(n->p[n], k) - p2 = ntuple(n->p[k+n], 5-k) + p1 = ntuple(n -> p[n], k) + p2 = ntuple(n -> p[k + n], 5 - k) t2 = @constinferred permute(t, p1, p2) @test norm(t2) ≈ norm(t) - t2′= permute(t′, p1, p2) - @test dot(t2′,t2) ≈ dot(t′,t) ≈ dot(transpose(t2′), transpose(t2)) + t2′ = permute(t′, p1, p2) + @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) end end end @@ -205,21 +204,21 @@ for V in spacelist @timedtestset "Permutations: test via conversion" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = Tensor(rand, ComplexF64, W) - for k = 0:5 + for k in 0:5 for p in permutations(1:5) - p1 = ntuple(n->p[n], k) - p2 = ntuple(n->p[k+n], 5-k) + p1 = ntuple(n -> p[n], k) + p2 = ntuple(n -> p[k + n], 5 - k) t2 = permute(t, p1, p2) a2 = convert(Array, t2) - @test a2 ≈ permutedims(convert(Array, t), (p1...,p2...)) - @test convert(Array, transpose(t2)) ≈ permutedims(a2, (5,4,3,2,1)) + @test a2 ≈ permutedims(convert(Array, t), (p1..., p2...)) + @test convert(Array, transpose(t2)) ≈ permutedims(a2, (5, 4, 3, 2, 1)) end end end end @timedtestset "Full trace: test self-consistency" begin t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') - t2 = permute(t, (1,2), (4,3)) + t2 = permute(t, (1, 2), (4, 3)) s = @constinferred tr(t2) @test conj(s) ≈ tr(t2') if !isdual(V1) @@ -229,24 +228,24 @@ for V in spacelist t2 = twist!(t2, 2) end ss = tr(t2) - @tensor s2 = t[a,b,b,a] - @tensor t3[a,b] := t[a,c,c,b] - @tensor s3 = t3[a,a] + @tensor s2 = t[a, b, b, a] + @tensor t3[a, b] := t[a, c, c, b] + @tensor s3 = t3[a, a] @test ss ≈ s2 @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') - @tensor t2[a,b] := t[c,d,b,d,c,a] - @tensor t4[a,b,c,d] := t[d,e,b,e,c,a] - @tensor t5[a,b] := t4[a,b,c,c] + @tensor t2[a, b] := t[c, d, b, d, c, a] + @tensor t4[a, b, c, d] := t[d, e, b, e, c, a] + @tensor t5[a, b] := t4[a, b, c, c] @test t2 ≈ t5 end if hasfusiontensor(I) @timedtestset "Trace: test via conversion" begin t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') - @tensor t2[a,b] := t[c,d,b,d,c,a] - @tensor t3[a,b] := convert(Array, t)[c,d,b,d,c,a] + @tensor t2[a, b] := t[c, d, b, d, c, a] + @tensor t3[a, b] := convert(Array, t)[c, d, b, d, c, a] @test t3 ≈ convert(Array, t2) end end @@ -254,25 +253,25 @@ for V in spacelist t1 = Tensor(rand, ComplexF64, V1 ⊗ V2 ⊗ V3) t2 = Tensor(rand, ComplexF64, V2' ⊗ V4 ⊗ V1') t3 = t1 ⊗ t2 - @tensor ta[a,b] := t1[x,y,a]*t2[y,b,x] - @tensor tb[a,b] := t3[x,y,a,y,b,x] + @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] + @tensor tb[a, b] := t3[x, y, a, y, b, x] @test ta ≈ tb end if hasfusiontensor(I) @timedtestset "Tensor contraction: test via conversion" begin - A1 = TensorMap(randn, ComplexF64, V1'*V2', V3') - A2 = TensorMap(randn, ComplexF64, V3*V4, V5) + A1 = TensorMap(randn, ComplexF64, V1' * V2', V3') + A2 = TensorMap(randn, ComplexF64, V3 * V4, V5) rhoL = TensorMap(randn, ComplexF64, V1, V1) rhoR = TensorMap(randn, ComplexF64, V5, V5)' # test adjoint tensor - H = TensorMap(randn, ComplexF64, V2*V4, V2*V4) + H = TensorMap(randn, ComplexF64, V2 * V4, V2 * V4) @tensor HrA12[a, s1, s2, c] := rhoL[a, a'] * conj(A1[a', t1, b]) * - A2[b, t2, c'] * rhoR[c', c] * H[s1, s2, t1, t2] + A2[b, t2, c'] * rhoR[c', c] * H[s1, s2, t1, t2] @tensor HrA12array[a, s1, s2, c] := convert(Array, rhoL)[a, a'] * - conj(convert(Array, A1)[a', t1, b]) * - convert(Array, A2)[b, t2, c'] * - convert(Array, rhoR)[c', c] * - convert(Array, H)[s1, s2, t1, t2] + conj(convert(Array, A1)[a', t1, b]) * + convert(Array, A2)[b, t2, c'] * + convert(Array, rhoR)[c', c] * + convert(Array, H)[s1, s2, t1, t2] @test HrA12array ≈ convert(Array, HrA12) end @@ -284,15 +283,15 @@ for V in spacelist t1 = TensorMap(rand, T, W1, W1) t2 = TensorMap(rand, T, W2, W2) t = TensorMap(rand, T, W1, W2) - @test t1*(t1\t) ≈ t - @test (t/t2)*t2 ≈ t - @test t1\one(t1) ≈ inv(t1) - @test one(t1)/t1 ≈ pinv(t1) + @test t1 * (t1 \ t) ≈ t + @test (t / t2) * t2 ≈ t + @test t1 \ one(t1) ≈ inv(t1) + @test one(t1) / t1 ≈ pinv(t1) @test_throws SpaceMismatch inv(t) - @test_throws SpaceMismatch t2\t - @test_throws SpaceMismatch t/t1 - tp = pinv(t)*t - @test tp ≈ tp*tp + @test_throws SpaceMismatch t2 \ t + @test_throws SpaceMismatch t / t1 + tp = pinv(t) * t + @test tp ≈ tp * tp end end if hasfusiontensor(I) @@ -308,10 +307,10 @@ for V in spacelist At1 = reshape(convert(Array, t1), d1, d1) At2 = reshape(convert(Array, t2), d2, d2) At = reshape(convert(Array, t), d1, d2) - @test reshape(convert(Array, t1*t), d1, d2) ≈ At1*At - @test reshape(convert(Array, t1'*t), d1, d2) ≈ At1'*At - @test reshape(convert(Array, t2*t'), d2, d1) ≈ At2*At' - @test reshape(convert(Array, t2'*t'), d2, d1) ≈ At2'*At' + @test reshape(convert(Array, t1 * t), d1, d2) ≈ At1 * At + @test reshape(convert(Array, t1' * t), d1, d2) ≈ At1' * At + @test reshape(convert(Array, t2 * t'), d2, d1) ≈ At2 * At' + @test reshape(convert(Array, t2' * t'), d2, d1) ≈ At2' * At' @test reshape(convert(Array, inv(t1)), d1, d1) ≈ inv(At1) @test reshape(convert(Array, pinv(t)), d2, d1) ≈ pinv(At) @@ -320,15 +319,15 @@ for V in spacelist continue end - @test reshape(convert(Array, t1\t), d1, d2) ≈ At1\At - @test reshape(convert(Array, t1'\t), d1, d2) ≈ At1'\At - @test reshape(convert(Array, t2\t'), d2, d1) ≈ At2\At' - @test reshape(convert(Array, t2'\t'), d2, d1) ≈ At2'\At' + @test reshape(convert(Array, t1 \ t), d1, d2) ≈ At1 \ At + @test reshape(convert(Array, t1' \ t), d1, d2) ≈ At1' \ At + @test reshape(convert(Array, t2 \ t'), d2, d1) ≈ At2 \ At' + @test reshape(convert(Array, t2' \ t'), d2, d1) ≈ At2' \ At' - @test reshape(convert(Array, t2/t), d2, d1) ≈ At2/At - @test reshape(convert(Array, t2'/t), d2, d1) ≈ At2'/At - @test reshape(convert(Array, t1/t'), d1, d2) ≈ At1/At' - @test reshape(convert(Array, t1'/t'), d1, d2) ≈ At1'/At' + @test reshape(convert(Array, t2 / t), d2, d1) ≈ At2 / At + @test reshape(convert(Array, t2' / t), d2, d1) ≈ At2' / At + @test reshape(convert(Array, t1 / t'), d1, d2) ≈ At1 / At' + @test reshape(convert(Array, t1' / t'), d1, d2) ≈ At1' / At' end end end @@ -338,71 +337,89 @@ for V in spacelist # Test both a normal tensor and an adjoint one. ts = (Tensor(rand, T, W), Tensor(rand, T, W)') for t in ts - @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), TensorKit.QL(), TensorKit.QLpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - Q, R = @constinferred leftorth(t, (3,4,2), (1,5); alg = alg) - QdQ = Q'*Q + @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) + QdQ = Q' * Q @test QdQ ≈ one(QdQ) - @test Q*R ≈ permute(t, (3,4,2), (1,5)) + @test Q * R ≈ permute(t, (3, 4, 2), (1, 5)) if alg isa Polar @test isposdef(R) @test domain(R) == codomain(R) == space(t, 1)' ⊗ space(t, 5)' end end - @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), TensorKit.SDD()) - N = @constinferred leftnull(t, (3,4,2),(1,5); alg = alg) - NdN = N'*N + @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), + TensorKit.SDD()) + N = @constinferred leftnull(t, (3, 4, 2), (1, 5); alg=alg) + NdN = N' * N @test NdN ≈ one(NdN) - @test norm(N'*permute(t, (3,4,2),(1,5))) < 100*eps(norm(t)) + @test norm(N' * permute(t, (3, 4, 2), (1, 5))) < 100 * eps(norm(t)) end - @testset "rightorth with $alg" for alg in (TensorKit.RQ(), TensorKit.RQpos(), TensorKit.LQ(), TensorKit.LQpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - L, Q = @constinferred rightorth(t, (3,4),(2,1,5); alg = alg) - QQd = Q*Q' + @testset "rightorth with $alg" for alg in + (TensorKit.RQ(), TensorKit.RQpos(), + TensorKit.LQ(), TensorKit.LQpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + L, Q = @constinferred rightorth(t, (3, 4), (2, 1, 5); alg=alg) + QQd = Q * Q' @test QQd ≈ one(QQd) - @test L*Q ≈ permute(t, (3,4),(2,1,5)) + @test L * Q ≈ permute(t, (3, 4), (2, 1, 5)) if alg isa Polar @test isposdef(L) @test domain(L) == codomain(L) == space(t, 3) ⊗ space(t, 4) end end - @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), TensorKit.SDD()) - M = @constinferred rightnull(t, (3,4),(2,1,5); alg = alg) - MMd = M*M' + @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), + TensorKit.SDD()) + M = @constinferred rightnull(t, (3, 4), (2, 1, 5); alg=alg) + MMd = M * M' @test MMd ≈ one(MMd) - @test norm(permute(t, (3,4),(2,1,5))*M') < 100*eps(norm(t)) + @test norm(permute(t, (3, 4), (2, 1, 5)) * M') < 100 * eps(norm(t)) end @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) - U, S, V = @constinferred tsvd(t, (3,4,2),(1,5); alg = alg) - UdU = U'*U + U, S, V = @constinferred tsvd(t, (3, 4, 2), (1, 5); alg=alg) + UdU = U' * U @test UdU ≈ one(UdU) - VVd = V*V' + VVd = V * V' @test VVd ≈ one(VVd) - @test U*S*V ≈ permute(t, (3,4,2),(1,5)) + @test U * S * V ≈ permute(t, (3, 4, 2), (1, 5)) end end @testset "empty tensor" begin t = TensorMap(randn, T, V1 ⊗ V2, typeof(V1)()) - @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), TensorKit.QL(), TensorKit.QLpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - Q, R = @constinferred leftorth(t; alg = alg) + @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + Q, R = @constinferred leftorth(t; alg=alg) @test Q == t @test dim(Q) == dim(R) == 0 end - @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), TensorKit.SDD()) - N = @constinferred leftnull(t; alg = alg) - @test N'*N ≈ id(domain(N)) - @test N*N' ≈ id(codomain(N)) + @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), + TensorKit.SDD()) + N = @constinferred leftnull(t; alg=alg) + @test N' * N ≈ id(domain(N)) + @test N * N' ≈ id(codomain(N)) end - @testset "rightorth with $alg" for alg in (TensorKit.RQ(), TensorKit.RQpos(), TensorKit.LQ(), TensorKit.LQpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - L, Q = @constinferred rightorth(copy(t'); alg = alg) + @testset "rightorth with $alg" for alg in + (TensorKit.RQ(), TensorKit.RQpos(), + TensorKit.LQ(), TensorKit.LQpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + L, Q = @constinferred rightorth(copy(t'); alg=alg) @test Q == t' @test dim(Q) == dim(L) == 0 end - @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), TensorKit.SDD()) - M = @constinferred rightnull(copy(t'); alg = alg) - @test M*M' ≈ id(codomain(M)) - @test M'*M ≈ id(domain(M)) + @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), + TensorKit.SDD()) + M = @constinferred rightnull(copy(t'); alg=alg) + @test M * M' ≈ id(codomain(M)) + @test M' * M ≈ id(domain(M)) end @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) - U, S, V = @constinferred tsvd(t; alg = alg) + U, S, V = @constinferred tsvd(t; alg=alg) @test U == t @test dim(U) == dim(S) == dim(V) end @@ -410,27 +427,27 @@ for V in spacelist t = Tensor(rand, T, V1 ⊗ V1' ⊗ V2 ⊗ V2') @testset "eig and isposdef" begin - D, V = eigen(t, (1,3), (2,4)) - D̃, Ṽ = @constinferred eig(t, (1,3), (2,4)) + D, V = eigen(t, (1, 3), (2, 4)) + D̃, Ṽ = @constinferred eig(t, (1, 3), (2, 4)) @test D ≈ D̃ @test V ≈ Ṽ - VdV = V'*V - VdV = (VdV + VdV')/2 + VdV = V' * V + VdV = (VdV + VdV') / 2 @test isposdef(VdV) - t2 = permute(t, (1,3), (2,4)) - @test t2*V ≈ V*D + t2 = permute(t, (1, 3), (2, 4)) + @test t2 * V ≈ V * D @test !isposdef(t2) # unlikely for non-hermitian map - t2 = (t2 + t2'); + t2 = (t2 + t2') D, V = eigen(t2) - VdV = V'*V + VdV = V' * V @test VdV ≈ one(VdV) D̃, Ṽ = @constinferred eigh(t2) @test D ≈ D̃ @test V ≈ Ṽ - λ = minimum(minimum(real(LinearAlgebra.diag(b))) for (c,b) in blocks(D)) + λ = minimum(minimum(real(LinearAlgebra.diag(b))) for (c, b) in blocks(D)) @test isposdef(t2) == isposdef(λ) - @test isposdef(t2 - λ*one(t2) + 0.1*one(t2)) - @test !isposdef(t2 - λ*one(t2) - 0.1*one(t2)) + @test isposdef(t2 - λ * one(t2) + 0.1 * one(t2)) + @test !isposdef(t2 - λ * one(t2) - 0.1 * one(t2)) end end end @@ -442,23 +459,23 @@ for V in spacelist TensorMap(randn, T, V4 ⊗ V5, V1 ⊗ V2 ⊗ V3)') for t in ts U₀, S₀, V₀, = tsvd(t) - t = rmul!(t, 1/norm(S₀, p)) - U, S, V, ϵ = @constinferred tsvd(t; trunc = truncerr(5e-1), p = p) + t = rmul!(t, 1 / norm(S₀, p)) + U, S, V, ϵ = @constinferred tsvd(t; trunc=truncerr(5e-1), p=p) # @show p, ϵ # @show domain(S) # @test min(space(S,1), space(S₀,1)) != space(S₀,1) - U′, S′, V′, ϵ′ = tsvd(t; trunc = truncerr(nextfloat(ϵ)), p = p) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncerr(nextfloat(ϵ)), p=p) @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) - U′, S′, V′, ϵ′ = tsvd(t; trunc = truncdim(ceil(Int, dim(domain(S)))), p = p) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncdim(ceil(Int, dim(domain(S)))), p=p) @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) - U′, S′, V′, ϵ′ = tsvd(t; trunc = truncspace(space(S,1)), p = p) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) # results with truncationcutoff cannot be compared because they don't take degeneracy into account, and thus truncate differently - U, S, V, ϵ = tsvd(t; trunc = truncbelow(1/dim(domain(S₀))), p = p) + U, S, V, ϵ = tsvd(t; trunc=truncbelow(1 / dim(domain(S₀))), p=p) # @show p, ϵ # @show domain(S) # @test min(space(S,1), space(S₀,1)) != space(S₀,1) - U′, S′, V′, ϵ′ = tsvd(t; trunc = truncspace(space(S,1)), p = p) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) end end @@ -471,23 +488,23 @@ for V in spacelist t = TensorMap(randn, T, W, W) s = dim(W) expt = @constinferred exp(t) - @test reshape(convert(Array, expt), (s,s)) ≈ - exp(reshape(convert(Array, t), (s,s))) + @test reshape(convert(Array, expt), (s, s)) ≈ + exp(reshape(convert(Array, t), (s, s))) @test (@constinferred sqrt(t))^2 ≈ t - @test reshape(convert(Array, sqrt(t^2)), (s,s)) ≈ - sqrt(reshape(convert(Array, t^2), (s,s))) + @test reshape(convert(Array, sqrt(t^2)), (s, s)) ≈ + sqrt(reshape(convert(Array, t^2), (s, s))) @test exp(@constinferred log(expt)) ≈ expt - @test reshape(convert(Array, log(expt)), (s,s)) ≈ - log(reshape(convert(Array, expt), (s,s))) + @test reshape(convert(Array, log(expt)), (s, s)) ≈ + log(reshape(convert(Array, expt), (s, s))) @test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ id(W) - @test (@constinferred tan(t)) ≈ sin(t)/cos(t) - @test (@constinferred cot(t)) ≈ cos(t)/sin(t) + @test (@constinferred tan(t)) ≈ sin(t) / cos(t) + @test (@constinferred cot(t)) ≈ cos(t) / sin(t) @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ id(W) - @test (@constinferred tanh(t)) ≈ sinh(t)/cosh(t) - @test (@constinferred coth(t)) ≈ cosh(t)/sinh(t) + @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) + @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t) t1 = sin(t) @test sin(@constinferred asin(t1)) ≈ t1 @@ -512,13 +529,14 @@ for V in spacelist for T in (Float32, ComplexF64) tA = TensorMap(rand, T, V1 ⊗ V3, V1 ⊗ V3) tB = TensorMap(rand, T, V2 ⊗ V4, V2 ⊗ V4) - tA = 3//2*leftorth(tA; alg = Polar())[1] - tB = 1//5*leftorth(tB; alg = Polar())[1] + tA = 3 // 2 * leftorth(tA; alg=Polar())[1] + tB = 1 // 5 * leftorth(tB; alg=Polar())[1] tC = TensorMap(rand, T, V1 ⊗ V3, V2 ⊗ V4) t = @constinferred sylvester(tA, tB, tC) @test codomain(t) == V1 ⊗ V3 @test domain(t) == V2 ⊗ V4 - @test norm(tA*t + t*tB + tC) < (norm(tA)+norm(tB)+norm(tC))*eps(real(T))^(2/3) + @test norm(tA * t + t * tB + tC) < + (norm(tA) + norm(tB) + norm(tC)) * eps(real(T))^(2 / 3) if hasfusiontensor(I) matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) @@ -545,8 +563,8 @@ for V in spacelist d4 = dim(domain(t2)) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ - reshape(convert(Array, t1), (d1, 1, d3, 1)) .* - reshape(convert(Array, t2), (1, d2, 1, d4)) + reshape(convert(Array, t1), (d1, 1, d3, 1)) .* + reshape(convert(Array, t2), (1, d2, 1, d4)) end end end @@ -555,14 +573,14 @@ for V in spacelist t1 = Tensor(rand, T, V2 ⊗ V3 ⊗ V1) t2 = Tensor(rand, T, V2 ⊗ V1 ⊗ V3) t = @constinferred (t1 ⊗ t2) - @tensor t′[1, 2, 3, 4, 5, 6] := t1[1,2,3]*t2[4,5,6] + @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] @test t ≈ t′ end end global tf = time() printstyled("Finished tensor tests with symmetry $Istr in ", - string(round(tf-ti; sigdigits=3)), - " seconds."; bold = true, color = Base.info_color()) + string(round(tf - ti; sigdigits=3)), + " seconds."; bold=true, color=Base.info_color()) println() end @@ -580,8 +598,8 @@ end d4 = dim(domain(t2)) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ - reshape(convert(Array, t1), (d1, 1, d3, 1)) .* - reshape(convert(Array, t2), (1, d2, 1, d4)) + reshape(convert(Array, t1), (d1, 1, d3, 1)) .* + reshape(convert(Array, t2), (1, d2, 1, d4)) end end end From 1d03961fe9ab6723eef8881e38314ee35dbfcf38 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Jun 2023 09:58:37 +0200 Subject: [PATCH 07/57] Fix arg order part II --- src/tensors/tensoroperations.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 6040e0a8..1b3371f5 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -324,12 +324,12 @@ end function scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} return dim(codomain(t)) == dim(domain(t)) == 1 ? - first(blocks(t))[2][1, 1] : throw(SpaceMismatch()) + first(blocks(t))[2][1, 1] : throw(DimensionMismatch()) end TO.tensorscalar(t::AbstractTensorMap) = scalar(t) -function TO.tensoradd!(tdst::AbstractTensorMap{S}{S}, +function TO.tensoradd!(tdst::AbstractTensorMap{S}, tsrc::AbstractTensorMap{S}, pA::Index2Tuple, conjA::Symbol, α::Number, β::Number) where {S} if conjA == :N @@ -464,19 +464,19 @@ function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, # return tC end -function TO.tensoradd_type(TC, ::AbstractTensorMap{S}, ::Index2Tuple{N₁,N₂}, +function TO.tensoradd_type(TC, ::Index2Tuple{N₁,N₂}, ::AbstractTensorMap{S}, ::Symbol) where {S,N₁,N₂} return tensormaptype(S, N₁, N₂, TC) end -function TO.tensoradd_structure(A::AbstractTensorMap{S}, pA::Index2Tuple{N₁,N₂}, +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), pA[1])) - dom = ProductSpace{S,N₂}(dual.(space.(Ref(A), pA[2]))) + cod = ProductSpace{S,N₁}(space.(Ref(A), pC[1])) + dom = ProductSpace{S,N₂}(dual.(space.(Ref(A), pC[2]))) return dom → cod else - return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pA), :N) + return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pC), :N) end end From 782b684574f6d253e3dcac8f0a874ee0c51db4df Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Jun 2023 10:00:10 +0200 Subject: [PATCH 08/57] Change eltype to scalartype --- src/tensors/abstracttensor.jl | 2 +- src/tensors/linalg.jl | 21 +++++++++++---------- src/tensors/tensor.jl | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index a7377773..a16d966d 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -107,7 +107,7 @@ end function Base.isapprox(t1::AbstractTensorMap, t2::AbstractTensorMap; atol::Real=0, - rtol::Real=Base.rtoldefault(eltype(t1), eltype(t2), atol)) + rtol::Real=Base.rtoldefault(scalartype(t1), scalartype(t2), atol)) d = norm(t1 - t2) if isfinite(d) return d <= max(atol, rtol * max(norm(t1), norm(t2))) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 1184a587..0e85da5c 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -2,22 +2,23 @@ #--------------- Base.copy(t::AbstractTensorMap) = Base.copy!(similar(t), t) -Base.:-(t::AbstractTensorMap) = mul!(similar(t), t, -one(eltype(t))) +Base.:-(t::AbstractTensorMap) = mul!(similar(t), t, -one(scalartype(t))) function Base.:+(t1::AbstractTensorMap, t2::AbstractTensorMap) - T = promote_type(eltype(t1), eltype(t2)) + T = promote_type(scalartype(t1), scalartype(t2)) return axpy!(one(T), t2, copy!(similar(t1, T), t1)) end function Base.:-(t1::AbstractTensorMap, t2::AbstractTensorMap) - T = promote_type(eltype(t1), eltype(t2)) + T = promote_type(scalartype(t1), scalartype(t2)) return axpy!(-one(T), t2, copy!(similar(t1, T), t1)) end -Base.:*(t::AbstractTensorMap, α::Number) = - mul!(similar(t, promote_type(eltype(t), typeof(α))), t, α) +function Base.:*(t::AbstractTensorMap, α::Number) + return mul!(similar(t, promote_type(scalartype(t), typeof(α))), t, α) +end Base.:*(α::Number, t::AbstractTensorMap) = - mul!(similar(t, promote_type(eltype(t), typeof(α))), α, t) -Base.:/(t::AbstractTensorMap, α::Number) = *(t, one(eltype(t))/α) -Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(eltype(t))/α) + mul!(similar(t, promote_type(scalartype(t), typeof(α))), α, t) +Base.:/(t::AbstractTensorMap, α::Number) = *(t, one(scalartype(t))/α) +Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(scalartype(t))/α) LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real = 2) = rmul!(t, inv(norm(t, p))) LinearAlgebra.normalize(t::AbstractTensorMap, p::Real = 2) = @@ -219,7 +220,7 @@ function LinearAlgebra.dot(t1::AbstractTensorMap, t2::AbstractTensorMap) space(t1) == space(t2) || throw(SpaceMismatch()) InnerProductStyle(t1) === EuclideanProduct() || throw(ArgumentError("dot requires Euclidean inner product")) - T = promote_type(eltype(t1), eltype(t2)) + T = promote_type(scalartype(t1), scalartype(t2)) s = zero(T) for c in blocksectors(t1) s += convert(T, dim(c)) * dot(block(t1, c), block(t2, c)) @@ -230,7 +231,7 @@ end function LinearAlgebra.norm(t::AbstractTensorMap, p::Real = 2) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("norm requires Euclidean inner product")) - return _norm(blocks(t), p, float(zero(real(eltype(t))))) + return _norm(blocks(t), p, float(zero(real(scalartype(t))))) end function _norm(blockiter, p::Real, init::Real) if p == Inf diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 2783322d..15b17ce7 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -548,7 +548,7 @@ end #--------------------------- Base.convert(::Type{TensorMap}, t::TensorMap) = t Base.convert(::Type{TensorMap}, t::AbstractTensorMap) = - copy!(TensorMap(undef, eltype(t), codomain(t), domain(t)), t) + copy!(TensorMap(undef, scalartype(t), codomain(t), domain(t)), t) function Base.convert(T::Type{TensorMap{S,N₁,N₂,I,A,F1,F2}}, t::AbstractTensorMap{S,N₁,N₂}) where {S,N₁,N₂,I,A,F1,F2} From 5b1f085ef232778ed4f05d8eed209943f0c840bc Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Jun 2023 10:00:46 +0200 Subject: [PATCH 09/57] Changes to @planar --- src/planar/planaroperations.jl | 165 +++++++++++++++++++++++---------- src/planar/postprocessors.jl | 97 +++++++++---------- 2 files changed, 163 insertions(+), 99 deletions(-) diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index c50d9c35..8744ea3f 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -1,79 +1,142 @@ # planar versions of tensor operations add!, trace! and contract! -planar_add!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S, N₁, N₂}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {S, N₁, N₂} = - add_transpose!(α, tsrc, β, tdst, p1, p2) +function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, + pA::Index2Tuple{N₁,N₂}, α, β) where {S,N₁,N₂} + return add_transpose!(α, A, β, C, pA...) +end -function planar_trace!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S, N₁, N₂}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S, N₁, N₂, N₃} +function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, pA::Index2Tuple{N₃,N₃}, + α, β) where {S,N₁,N₂,N₃} if BraidingStyle(sectortype(S)) == Bosonic() - return trace!(α, tsrc, β, tdst, p1, p2, q1, q2) + return tensortrace!(C, pC, A, pA, conjA, α, β) end - + @boundscheck begin - all(i->space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || - throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i->space(tsrc, p2[i]) == space(tdst, N₁+i), 1:N₂) || - throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i->space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || - throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), + all(i -> space(A, pC[1][i]) == space(C, i), 1:N₁) || + throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), + C = $(codomain(C))←$(domain(C)), p1 = $(p1), p2 = $(p2)")) + all(i -> space(A, pC[2][i]) == space(C, N₁ + i), 1:N₂) || + throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), + C = $(codomain(C))←$(domain(C)), p1 = $(p1), p2 = $(p2)")) + all(i -> space(A, pA[1][i]) == dual(space(A, pA[2][i])), 1:N₃) || + throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), q1 = $(q1), q2 = $(q2)")) end - + if iszero(β) - fill!(tdst, β) + fill!(C, β) elseif β != 1 - rmul!(tdst, β) + rmul!(C, β) end - pdata = (p1..., p2...) - for (f1, f2) in fusiontrees(tsrc) - for ((f1′, f2′), coeff) in planar_trace(f1, f2, p1, p2, q1, q2) - TO._trace!(α*coeff, tsrc[f1, f2], true, tdst[f1′, f2′], pdata, q1, q2) + + pdata = linearize(pC) + for (f1, f2) in fusiontrees(A) + for ((f1′, f2′), coeff) in planar_trace(f1, f2, pC..., pA...) + TO._trace!(α * coeff, A[f1, f2], true, C[f1′, f2′], pdata, pA...) end end - return tdst + return C end -function planar_contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple, cindA::IndexTuple, - oindB::IndexTuple, cindB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}} = nothing) where {S} - +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, pB::Index2Tuple, α, β) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, pA..., pB[2], pB[1], pC...) + if oindA == codA && cindA == domA A′ = A else - if isnothing(syms) - A′ = TO.similar_from_indices(eltype(A), oindA, cindA, A, :N) - else - A′ = TO.cached_similar_from_indices(syms[1], eltype(A), oindA, cindA, A, :N) - end + A′ = TO.tensoralloc_add(scalartype(A), (oindA, cindA), A, :N) add_transpose!(true, A, false, A′, oindA, cindA) end + if cindB == codB && oindB == domB B′ = B else - if isnothing(syms) - B′ = TO.similar_from_indices(eltype(B), cindB, oindB, B, :N) - else - B′ = TO.cached_similar_from_indices(syms[2], eltype(B), cindB, oindB, B, :N) - end + B′ = TensorOperations.tensoralloc_add(scalartype(B), (cindB, oindB), B, :N) add_transpose!(true, B, false, B′, cindB, oindB) end mul!(C, A′, B′, α, β) + return C end +# function planaradd!(α, tsrc::AbstractTensorMap{S}, +# β, tdst::AbstractTensorMap{S,N₁,N₂}, +# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {S,N₁,N₂} +# return add_transpose!(α, tsrc, β, tdst, p1, p2) +# end + +# function planar_trace!(α, tsrc::AbstractTensorMap{S}, +# β, tdst::AbstractTensorMap{S,N₁,N₂}, +# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, +# q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S,N₁,N₂,N₃} +# if BraidingStyle(sectortype(S)) == Bosonic() +# return trace!(α, tsrc, β, tdst, p1, p2, q1, q2) +# end + +# @boundscheck begin +# all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || +# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), +# tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) +# all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || +# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), +# tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) +# all(i -> space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || +# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), +# q1 = $(q1), q2 = $(q2)")) +# end + +# if iszero(β) +# fill!(tdst, β) +# elseif β != 1 +# rmul!(tdst, β) +# end +# pdata = (p1..., p2...) +# for (f1, f2) in fusiontrees(tsrc) +# for ((f1′, f2′), coeff) in planar_trace(f1, f2, p1, p2, q1, q2) +# TO._trace!(α * coeff, tsrc[f1, f2], true, tdst[f1′, f2′], pdata, q1, q2) +# end +# end +# return tdst +# end + +# function planar_contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, +# β, C::AbstractTensorMap{S}, +# oindA::IndexTuple, cindA::IndexTuple, +# oindB::IndexTuple, cindB::IndexTuple, +# p1::IndexTuple, p2::IndexTuple, +# syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S} +# codA, domA = codomainind(A), domainind(A) +# codB, domB = codomainind(B), domainind(B) +# oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, +# oindB, cindB, p1, p2) + +# if oindA == codA && cindA == domA +# A′ = A +# else +# if isnothing(syms) +# A′ = TO.similar_from_indices(eltype(A), oindA, cindA, A, :N) +# else +# A′ = TO.cached_similar_from_indices(syms[1], eltype(A), oindA, cindA, A, :N) +# end +# add_transpose!(true, A, false, A′, oindA, cindA) +# end +# if cindB == codB && oindB == domB +# B′ = B +# else +# if isnothing(syms) +# B′ = TO.similar_from_indices(eltype(B), cindB, oindB, B, :N) +# else +# B′ = TO.cached_similar_from_indices(syms[2], eltype(B), cindB, oindB, B, :N) +# end +# add_transpose!(true, B, false, B′, cindB, oindB) +# end +# mul!(C, A′, B′, α, β) +# return C +# end + # auxiliary routines _cyclicpermute(t::Tuple) = (Base.tail(t)..., t[1]) _cyclicpermute(t::Tuple{}) = () @@ -95,10 +158,10 @@ function reorder_indices(codA, domA, codB, domB, oindA, oindB, p1, p2) while length(oindB2) > 0 && indB[end] != oindB2[1] indB = _cyclicpermute(indB) end - for i = 2:N₁ + for i in 2:N₁ @assert indA[i] == oindA2[i] end - for j = 2:N₂ + for j in 2:N₂ @assert indB[end + 1 - j] == oindB2[j] end Nc = length(indA) - N₁ @@ -110,12 +173,12 @@ function reorder_indices(codA, domA, codB, domB, oindA, oindB, p1, p2) end function reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - oindA2, cindA2, oindB2, cindB2 = - reorder_indices(codA, domA, codB, domB, oindA, oindB, p1, p2) + oindA2, cindA2, oindB2, cindB2 = reorder_indices(codA, domA, codB, domB, oindA, oindB, + p1, p2) #if oindA or oindB are empty, then reorder indices can only order it correctly up to a cyclic permutation! if isempty(oindA2) && !isempty(cindA) - # isempty(cindA) is a cornercase which I'm not sure if we can encounter + # isempty(cindA) is a cornercase which I'm not sure if we can encounter hit = cindA[findfirst(==(first(cindB2)), cindB)] while hit != first(cindA2) cindA2 = _cyclicpermute(cindA2) diff --git a/src/planar/postprocessors.jl b/src/planar/postprocessors.jl index c7305757..57d65414 100644 --- a/src/planar/postprocessors.jl +++ b/src/planar/postprocessors.jl @@ -4,23 +4,24 @@ # are not identified by the parsing step of TensorOperations, so we have to manually fix this # Step 1: we have to find the new name that TO.tensorify assigned to these temporaries # since it parses `tmp[] := a[] * b[]` as `newtmp = similar...; tmp = contract!(.... , newtmp, ...)` + +const _PLANAR_OPERATIONS = (:planaradd!, :planarcontract!, :planartrace!) +const _TENSOR_OPERATIONS = (:tensoradd!, :tensorcontract!, :tensortrace!) + +_is_tensoroperation(ex::Expr) = ex.head == :call && (ex.args[1] in _TENSOR_OPERATIONS) +_is_tensoroperation(ex) = false + function _update_temporaries(ex, temporaries) if ex isa Expr && ex.head == :(=) lhs = ex.args[1] i = findfirst(==(lhs), temporaries) if i !== nothing rhs = ex.args[2] - if rhs isa Expr && rhs.head == :call && rhs.args[1] == :add! - newname = rhs.args[6] - temporaries[i] = newname - elseif rhs isa Expr && rhs.head == :call && rhs.args[1] == :contract! - newname = rhs.args[8] - temporaries[i] = newname - elseif rhs isa Expr && rhs.head == :call && rhs.args[1] == :trace! - newname = rhs.args[6] - temporaries[i] = newname + if _is_tensoroperation(rhs) + temporaries[i] = rhs.args[2] else - @error "lhs = $lhs , rhs = $rhs" + temporaries[i] = rhs + # error("lhs = $lhs , rhs = $rhs") end end elseif ex isa Expr @@ -32,50 +33,50 @@ function _update_temporaries(ex, temporaries) end # Step 2: we find `newtmp = similar_from_...` and replace with `newtmp = cached_similar_from...` +# this is not necessary anymore? function _annotate_temporaries(ex, temporaries) - if ex isa Expr && ex.head == :(=) - lhs = ex.args[1] - i = findfirst(==(lhs), temporaries) - if i !== nothing - rhs = ex.args[2] - if !(rhs isa Expr && rhs.head == :call && rhs.args[1] == :similar_from_indices) - @error "lhs = $lhs , rhs = $rhs" - end - newrhs = Expr(:call, :cached_similar_from_indices, - QuoteNode(lhs), rhs.args[2:end]...) - return Expr(:(=), lhs, newrhs) - end - elseif ex isa Expr - return Expr(ex.head, [_annotate_temporaries(a, temporaries) for a in ex.args]...) - end + # if ex isa Expr && ex.head == :(=) + # lhs = ex.args[1] + # i = findfirst(==(lhs), temporaries) + # if i !== nothing + # rhs = ex.args[2] + # if !(rhs isa Expr && rhs.head == :call && rhs.args[1] == :similar_from_indices) + # @error "lhs = $lhs , rhs = $rhs" + # end + # newrhs = Expr(:call, :cached_similar_from_indices, + # QuoteNode(lhs), rhs.args[2:end]...) + # return Expr(:(=), lhs, newrhs) + # end + # elseif ex isa Expr + # return Expr(ex.head, [_annotate_temporaries(a, temporaries) for a in ex.args]...) + # end return ex end # add correct modules (`GlobalRef`) to various functions -const _TOFUNCTIONS = (:similar_from_indices, :cached_similar_from_indices, - :scalar, :IndexError) - function _add_modules(ex::Expr) - if ex.head == :call && ex.args[1] in _TOFUNCTIONS - return Expr(ex.head, GlobalRef(TensorOperations, ex.args[1]), - (_add_modules(ex.args[i]) for i in 2:length(ex.args))...) - elseif ex.head == :call && ex.args[1] == :add! - @assert ex.args[4] == :(:N) - argind = [2,3,5,6,7,8] - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_add!)), - (_add_modules(ex.args[i]) for i in argind)...) - elseif ex.head == :call && ex.args[1] == :trace! - @assert ex.args[4] == :(:N) - argind = [2,3,5,6,7,8,9,10] - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_trace!)), - (_add_modules(ex.args[i]) for i in argind)...) - elseif ex.head == :call && ex.args[1] == :contract! - @assert ex.args[4] == :(:N) && ex.args[6] == :(:N) - argind = vcat([2,3,5], 7:length(ex.args)) - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_contract!)), - (_add_modules(ex.args[i]) for i in argind)...) - else - return Expr(ex.head, (_add_modules(e) for e in ex.args)...) + if ex.head == :call + if ex.args[1] == :tensoradd! + conjA = popat!(ex.args, 5) + @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planaradd!)), + map(_add_modules, ex.args[2:end])...) + elseif ex.args[1] == :tensorcontract! + conjB = popat!(ex.args, 9) + conjA = popat!(ex.args, 6) + @assert conjA == conjB == :(:N) "conj flag should be `:N` ($conjA), ($conjB)" + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planarcontract!)), + map(_add_modules, ex.args[2:end])...) + elseif ex.args[1] == :tensortrace! + conjA = popat!(ex.args, 6) + @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planartrace!)), + map(_add_modules, ex.args[2:end])...) + elseif ex.args[1] in TensorOperations.tensoroperationsfunctions + return Expr(ex.head, GlobalRef(TensorOperations, ex.args[1]), + map(_add_modules, ex.args[2:end])...) + end end + return Expr(ex.head, (_add_modules(e) for e in ex.args)...) end _add_modules(ex) = ex From 65a2c20155e8448778cb42fec680861ae8b2436f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Jun 2023 10:00:58 +0200 Subject: [PATCH 10/57] Fix deprecated warnings --- src/tensors/truncation.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tensors/truncation.jl b/src/tensors/truncation.jl index c3f7aec5..e480406e 100644 --- a/src/tensors/truncation.jl +++ b/src/tensors/truncation.jl @@ -192,7 +192,7 @@ function _truncate!(V::SectorVectorDict, trunc::TruncationCutoff, p = 2) end # Combine truncations -struct MultipleTruncation{T<:Tuple{Vararg{<:TruncationScheme}}} <: TruncationScheme +struct MultipleTruncation{T<:Tuple{Vararg{TruncationScheme}}} <: TruncationScheme truncations::T end Base.:&(a::MultipleTruncation, b::MultipleTruncation) = @@ -207,7 +207,7 @@ function _truncate!(v, trunc::MultipleTruncation, p::Real = 2) v, truncerrs = __truncate!(v, trunc.truncations, p) return v, norm(truncerrs, p) end -function __truncate!(v, trunc::Tuple{Vararg{<:TruncationScheme}}, p::Real = 2) +function __truncate!(v, trunc::Tuple{Vararg{TruncationScheme}}, p::Real = 2) v, truncerr1 = _truncate!(v, first(trunc), p) v, truncerrtail = __truncate!(v, tail(trunc), p) return v, (truncerr1, truncerrtail...) From 48a709a6ae827d5810258f1fb05a9adfc4e17b0c Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 00:05:20 +0200 Subject: [PATCH 11/57] temporarily disable SU3 tests --- Project.toml | 3 +-- test/runtests.jl | 12 ++++++------ test/tensors.jl | 20 ++++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Project.toml b/Project.toml index 45b2baa2..75941883 100644 --- a/Project.toml +++ b/Project.toml @@ -27,11 +27,10 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SUNRepresentations = "1a50b95c-7aac-476d-a9ce-2bfc675fc617" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" [targets] -test = ["Combinatorics", "HalfIntegers", "LinearAlgebra", "Random", "SUNRepresentations", "TensorOperations", "Test", "TestExtras", "WignerSymbols"] +test = ["Combinatorics", "HalfIntegers", "LinearAlgebra", "Random", "TensorOperations", "Test", "TestExtras", "WignerSymbols"] diff --git a/test/runtests.jl b/test/runtests.jl index e4314de8..7a3c543b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,8 +6,8 @@ using Combinatorics using TensorKit: ProductSector, fusiontensor, pentagon_equation, hexagon_equation using TensorOperations using Base.Iterators: take, product -using SUNRepresentations: SUNIrrep -const SU3Irrep = SUNIrrep{3} +# using SUNRepresentations: SUNIrrep +# const SU3Irrep = SUNIrrep{3} using LinearAlgebra: LinearAlgebra include("newsectors.jl") @@ -50,16 +50,16 @@ function hasfusiontensor(I::Type{<:Sector}) end end -sectorlist = (Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, NewSU2Irrep, SU3Irrep, +sectorlist = (Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, NewSU2Irrep, # SU3Irrep, FibonacciAnyon, IsingAnyon, FermionParity, FermionNumber, FermionSpin, FermionParity ⊠ FermionParity, Z3Irrep ⊠ Z4Irrep, FermionNumber ⊠ SU2Irrep, FermionSpin ⊠ SU2Irrep, NewSU2Irrep ⊠ NewSU2Irrep, NewSU2Irrep ⊠ SU2Irrep, FermionSpin ⊠ NewSU2Irrep, Z2Irrep ⊠ FibonacciAnyon ⊠ FibonacciAnyon) Ti = time() -include("sectors.jl") -include("fusiontrees.jl") -include("spaces.jl") +# include("sectors.jl") +# include("fusiontrees.jl") +# include("spaces.jl") include("tensors.jl") Tf = time() printstyled("Finished all tests in ", diff --git a/test/tensors.jl b/test/tensors.jl index 951dcd22..3d59c654 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -43,13 +43,13 @@ VfSU₂ = (ℂ[FermionSpin](0 => 3, 1 // 2 => 1), ℂ[FermionSpin](1 // 2 => 1, 1 => 1)', ℂ[FermionSpin](0 => 2, 1 // 2 => 2), ℂ[FermionSpin](0 => 1, 1 // 2 => 1, 3 // 2 => 1)') -VSU₃ = (ℂ[SU3Irrep]((0, 0, 0) => 3, (1, 0, 0) => 1), - ℂ[SU3Irrep]((0, 0, 0) => 3, (2, 0, 0) => 1)', - ℂ[SU3Irrep]((1, 1, 0) => 1, (2, 1, 0) => 1), - ℂ[SU3Irrep]((1, 0, 0) => 1, (2, 0, 0) => 1), - ℂ[SU3Irrep]((0, 0, 0) => 1, (1, 0, 0) => 1, (1, 1, 0) => 1)') +# VSU₃ = (ℂ[SU3Irrep]((0, 0, 0) => 3, (1, 0, 0) => 1), +# ℂ[SU3Irrep]((0, 0, 0) => 3, (2, 0, 0) => 1)', +# ℂ[SU3Irrep]((1, 1, 0) => 1, (2, 1, 0) => 1), +# ℂ[SU3Irrep]((1, 0, 0) => 1, (2, 0, 0) => 1), +# ℂ[SU3Irrep]((0, 0, 0) => 1, (1, 0, 0) => 1, (1, 1, 0) => 1)') -for V in (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₃) +for V in (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂)#, VSU₃) V1, V2, V3, V4, V5 = V @assert V3 * V4 * V2 ≿ V1' * V5' # necessary for leftorth tests @assert V3 * V4 ≾ V1' * V2' * V5' # necessary for rightorth tests @@ -61,15 +61,15 @@ spacelist = try if Sys.iswindows() (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂) elseif Sys.isapple() - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂)#, VSU₃) else - (Vtr, Vℤ₂, Vfℤ₂, VU₁, VCU₁, VSU₂, VfSU₂, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, VU₁, VCU₁, VSU₂, VfSU₂)#, VSU₃) end else - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂)#, VSU₃) end catch - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂)#, VSU₃) end for V in spacelist From 3951a4f275fdb2aaa141ac54346be5881f53e828 Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 00:12:27 +0200 Subject: [PATCH 12/57] enable all tests --- test/runtests.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 7a3c543b..8492ec1c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -57,9 +57,9 @@ sectorlist = (Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, NewSU2Irre FermionSpin ⊠ NewSU2Irrep, Z2Irrep ⊠ FibonacciAnyon ⊠ FibonacciAnyon) Ti = time() -# include("sectors.jl") -# include("fusiontrees.jl") -# include("spaces.jl") +include("sectors.jl") +include("fusiontrees.jl") +include("spaces.jl") include("tensors.jl") Tf = time() printstyled("Finished all tests in ", From 4bc4f1665064c4f5df3d43dfea00b3a3430cd0cf Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 01:55:47 +0200 Subject: [PATCH 13/57] fix trace --- src/tensors/tensoroperations.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 1b3371f5..2a1c63ec 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -177,15 +177,13 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) - pdata = (p1..., p2...) - TO._trace!(α, tsrc[], β, tdst[], pdata, q1, q2) + TO.tensortrace!(tdst[], (p1, p2), tsrc[], (q1, q2), :N, α, β) # elseif FusionStyle(I) isa UniqueFusion # TODO: is it worth multithreading UniqueFusion case for traces? else cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) - pdata = (p1..., p2...) if iszero(β) fill!(tdst, β) elseif β != 1 @@ -204,8 +202,7 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N coeff *= twist(g1.uncoupled[i]) end end - TO._trace!(α * coeff, tsrc[f1, f2], true, tdst[f1′′, f2′′], pdata, q1, - q2) + TO.tensortrace!(tdst[f1′′, f2′′], (p1, p2), tsrc[f1, f2], (q1, q2), :N, α*coeff, true) end end end From 1e2b2102b7fc79bfef699fd1270401f479903b3d Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 01:56:24 +0200 Subject: [PATCH 14/57] prettify spaces code --- src/spaces/cartesianspace.jl | 8 ++-- src/spaces/complexspace.jl | 20 ++++---- src/spaces/deligne.jl | 58 ++++++++++++------------ src/spaces/gradedspace.jl | 40 ++++++++-------- src/spaces/homspace.jl | 4 +- src/spaces/productspace.jl | 10 ++-- src/spaces/vectorspaces.jl | 88 +++++++++++++++++------------------- 7 files changed, 112 insertions(+), 116 deletions(-) diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index d46b9bd0..10ddda9f 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -42,11 +42,11 @@ dim(V::CartesianSpace) = V.d Base.axes(V::CartesianSpace) = Base.OneTo(dim(V)) Base.oneunit(::Type{CartesianSpace}) = CartesianSpace(1) -⊕(V1::CartesianSpace, V2::CartesianSpace) = CartesianSpace(V1.d+V2.d) -fuse(V1::CartesianSpace, V2::CartesianSpace) = CartesianSpace(V1.d*V2.d) +⊕(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d+V₂.d) +fuse(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d*V₂.d) flip(V::CartesianSpace) = V -infimum(V1::CartesianSpace, V2::CartesianSpace) = CartesianSpace(min(V1.d, V2.d)) -supremum(V1::CartesianSpace, V2::CartesianSpace) = CartesianSpace(max(V1.d, V2.d)) +infimum(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(min(V₁.d, V₂.d)) +supremum(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(max(V₁.d, V₂.d)) Base.show(io::IO, V::CartesianSpace) = print(io, "ℝ^$(V.d)") diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index 7f443d77..f0784fcb 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -44,22 +44,22 @@ Base.axes(V::ComplexSpace) = Base.OneTo(dim(V)) Base.conj(V::ComplexSpace) = ComplexSpace(dim(V), !isdual(V)) Base.oneunit(::Type{ComplexSpace}) = ComplexSpace(1) -function ⊕(V1::ComplexSpace, V2::ComplexSpace) - return isdual(V1) == isdual(V2) ? - ComplexSpace(dim(V1) + dim(V2), isdual(V1)) : +function ⊕(V₁::ComplexSpace, V₂::ComplexSpace) + return isdual(V₁) == isdual(V₂) ? + ComplexSpace(dim(V₁) + dim(V₂), isdual(V₁)) : throw(SpaceMismatch("Direct sum of a vector space and its dual does not exist")) end -fuse(V1::ComplexSpace, V2::ComplexSpace) = ComplexSpace(V1.d * V2.d) +fuse(V₁::ComplexSpace, V₂::ComplexSpace) = ComplexSpace(V₁.d * V₂.d) flip(V::ComplexSpace) = dual(V) -function infimum(V1::ComplexSpace, V2::ComplexSpace) - return isdual(V1) == isdual(V2) ? - ComplexSpace(min(dim(V1), dim(V2)), isdual(V1)) : +function infimum(V₁::ComplexSpace, V₂::ComplexSpace) + return isdual(V₁) == isdual(V₂) ? + ComplexSpace(min(dim(V₁), dim(V₂)), isdual(V₁)) : throw(SpaceMismatch("Infimum of space and dual space does not exist")) end -function supremum(V1::ComplexSpace, V2::ComplexSpace) - return isdual(V1) == isdual(V2) ? - ComplexSpace(max(dim(V1), dim(V2)), isdual(V1)) : +function supremum(V₁::ComplexSpace, V₂::ComplexSpace) + return isdual(V₁) == isdual(V₂) ? + ComplexSpace(max(dim(V₁), dim(V₂)), isdual(V₁)) : throw(SpaceMismatch("Supremum of space and dual space does not exist")) end diff --git a/src/spaces/deligne.jl b/src/spaces/deligne.jl index 4a321837..2a8ea638 100644 --- a/src/spaces/deligne.jl +++ b/src/spaces/deligne.jl @@ -12,50 +12,50 @@ The Deligne tensor product also works in the type domain and for sectors and ten 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. """ -⊠(V1::VectorSpace, V2::VectorSpace) = (V1 ⊠ one(V2)) ⊗ (one(V1) ⊠ V2) +⊠(V₁::VectorSpace, V₂::VectorSpace) = (V₁ ⊠ one(V₂)) ⊗ (one(V₁) ⊠ V₂) # define deligne products with empty tensor product: just add a trivial sector of the type of the empty space to each of the sectors in the non-empty space -function ⊠(V::GradedSpace, P0::ProductSpace{<:ElementarySpace{ℂ},0}) - I1 = sectortype(V) - I2 = sectortype(P0) - return Vect[I1 ⊠ I2](ifelse(isdual(V), dual(c), c) ⊠ one(I2) => dim(V, c) for c in sectors(V); dual = isdual(V)) +function ⊠(V::GradedSpace, P₀::ProductSpace{<:ElementarySpace{ℂ},0}) + I₁ = sectortype(V) + I₂ = sectortype(P₀) + return Vect[I₁ ⊠ I₂](ifelse(isdual(V), dual(c), c) ⊠ one(I₂) => dim(V, c) for c in sectors(V); dual = isdual(V)) end -function ⊠(P0::ProductSpace{<:ElementarySpace{ℂ},0}, V::GradedSpace) - I1 = sectortype(P0) - I2 = sectortype(V) - return Vect[I1 ⊠ I2](one(I1) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) for c in sectors(V); dual = isdual(V)) +function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, V::GradedSpace) + I₁ = sectortype(P₀) + I₂ = sectortype(V) + return Vect[I₁ ⊠ I₂](one(I₁) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) for c in sectors(V); dual = isdual(V)) end -function ⊠(V::ComplexSpace, P0::ProductSpace{<:ElementarySpace{ℂ},0}) - I2 = sectortype(P0) - return Vect[I2](one(I2) => dim(V); dual = isdual(V)) +function ⊠(V::ComplexSpace, P₀::ProductSpace{<:ElementarySpace{ℂ},0}) + I₂ = sectortype(P₀) + return Vect[I₂](one(I₂) => dim(V); dual = isdual(V)) end -function ⊠(P0::ProductSpace{<:ElementarySpace{ℂ},0}, V::ComplexSpace) - I1 = sectortype(P0) - return Vect[I1](one(I1) => dim(V); dual = isdual(V)) +function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, V::ComplexSpace) + I₁ = sectortype(P₀) + return Vect[I₁](one(I₁) => dim(V); dual = isdual(V)) end function ⊠(P::ProductSpace{<:ElementarySpace{ℂ},0}, - P0::ProductSpace{<:ElementarySpace{ℂ},0}) - I1 = sectortype(P) - I2 = sectortype(P0) - return one(Vect[I1 ⊠ I2]) + P₀::ProductSpace{<:ElementarySpace{ℂ},0}) + I₁ = sectortype(P) + I₂ = sectortype(P₀) + return one(Vect[I₁ ⊠ I₂]) end -function ⊠(P::ProductSpace{<:ElementarySpace{ℂ}}, P0::ProductSpace{<:ElementarySpace{ℂ},0}) - I1 = sectortype(P) - I2 = sectortype(P0) - S = Vect[I1 ⊠ I2] +function ⊠(P::ProductSpace{<:ElementarySpace{ℂ}}, P₀::ProductSpace{<:ElementarySpace{ℂ},0}) + I₁ = sectortype(P) + I₂ = sectortype(P₀) + S = Vect[I₁ ⊠ I₂] N = length(P) - return ProductSpace{S,N}(map(V->V ⊠ P0, tuple(P...))) + return ProductSpace{S,N}(map(V->V ⊠ P₀, tuple(P...))) end -function ⊠(P0::ProductSpace{<:ElementarySpace{ℂ},0}, P::ProductSpace{<:ElementarySpace{ℂ}}) - I1 = sectortype(P0) - I2 = sectortype(P) - S = Vect[I1 ⊠ I2] +function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, P::ProductSpace{<:ElementarySpace{ℂ}}) + I₁ = sectortype(P₀) + I₂ = sectortype(P) + S = Vect[I₁ ⊠ I₂] N = length(P) - return ProductSpace{S,N}(map(V->P0 ⊠ V, tuple(P...))) + return ProductSpace{S,N}(map(V->P₀ ⊠ V, tuple(P...))) end diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 3489da4b..7146b64f 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -105,8 +105,8 @@ Base.conj(V::GradedSpace) = typeof(V)(V.dims, !V.dual) isdual(V::GradedSpace) = V.dual # equality / comparison -Base.:(==)(V1::GradedSpace, V2::GradedSpace) = - sectortype(V1) == sectortype(V2) && (V1.dims == V2.dims) && V1.dual == V2.dual +Base.:(==)(V₁::GradedSpace, V₂::GradedSpace) = + sectortype(V₁) == sectortype(V₂) && (V₁.dims == V₂.dims) && V₁.dual == V₂.dual # axes Base.axes(V::GradedSpace) = Base.OneTo(dim(V)) @@ -124,16 +124,16 @@ Base.oneunit(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I)=>1) # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so # these generic definitions (which are still quite efficient) are good for now. -function ⊕(V1::GradedSpace{I}, V2::GradedSpace{I}) where {I<:Sector} - dual1 = isdual(V1) - dual1 == isdual(V2) || +function ⊕(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} + dual1 = isdual(V₁) + dual1 == isdual(V₂) || throw(SpaceMismatch("Direct sum of a vector space and a dual space does not exist")) dims = SectorDict{I, Int}() - for c in union(sectors(V1), sectors(V2)) + for c in union(sectors(V₁), sectors(V₂)) cout = ifelse(dual1, dual(c), c) - dims[cout] = dim(V1, c) + dim(V2, c) + dims[cout] = dim(V₁, c) + dim(V₂, c) end - return typeof(V1)(dims; dual = dual1) + return typeof(V₁)(dims; dual = dual1) end function flip(V::GradedSpace{I}) where {I<:Sector} @@ -144,29 +144,29 @@ function flip(V::GradedSpace{I}) where {I<:Sector} end end -function fuse(V1::GradedSpace{I}, V2::GradedSpace{I}) where {I<:Sector} +function fuse(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} dims = SectorDict{I, Int}() - for a in sectors(V1), b in sectors(V2) + for a in sectors(V₁), b in sectors(V₂) for c in a ⊗ b - dims[c] = get(dims, c, 0) + Nsymbol(a, b, c)*dim(V1, a)*dim(V2, b) + dims[c] = get(dims, c, 0) + Nsymbol(a, b, c)*dim(V₁, a)*dim(V₂, b) end end - return typeof(V1)(dims) + return typeof(V₁)(dims) end -function infimum(V1::GradedSpace{I}, V2::GradedSpace{I}) where {I<:Sector} - if V1.dual == V2.dual - typeof(V1)(c=>min(dim(V1, c), dim(V2, c)) for c in - union(sectors(V1), sectors(V2)), dual = V1.dual) +function infimum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} + if V₁.dual == V₂.dual + typeof(V₁)(c=>min(dim(V₁, c), dim(V₂, c)) for c in + union(sectors(V₁), sectors(V₂)), dual = V₁.dual) else throw(SpaceMismatch("Infimum of space and dual space does not exist")) end end -function supremum(V1::GradedSpace{I}, V2::GradedSpace{I}) where {I<:Sector} - if V1.dual == V2.dual - typeof(V1)(c=>max(dim(V1, c), dim(V2, c)) for c in - union(sectors(V1), sectors(V2)), dual = V1.dual) +function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} + if V₁.dual == V₂.dual + typeof(V₁)(c=>max(dim(V₁, c), dim(V₂, c)) for c in + union(sectors(V₁), sectors(V₂)), dual = V₁.dual) else throw(SpaceMismatch("Supremum of space and dual space does not exist")) end diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index bf113767..6d390e65 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -23,8 +23,8 @@ function Base.adjoint(W::HomSpace{S}) where {S} end Base.hash(W::HomSpace, h::UInt) = hash(domain(W), hash(codomain(W), h)) -Base.:(==)(W1::HomSpace, W2::HomSpace) = - (W1.codomain == W2.codomain) && (W1.domain == W2.domain) +Base.:(==)(W₁::HomSpace, W₂::HomSpace) = + (W₁.codomain == W₂.codomain) && (W₁.domain == W₂.domain) spacetype(W::HomSpace) = spacetype(typeof(W)) sectortype(W::HomSpace) = sectortype(typeof(W)) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index c4418d32..a2c338f9 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -151,11 +151,11 @@ Base.hash(P::ProductSpace, h::UInt) = hash(P.spaces, h) # Default construction from product of spaces #--------------------------------------------- -⊗(V1::S, V2::S) where {S<:ElementarySpace}= ProductSpace((V1, V2)) -⊗(P1::ProductSpace{S}, V2::S) where {S<:ElementarySpace} = - ProductSpace(tuple(P1.spaces..., V2)) -⊗(V1::S, P2::ProductSpace{S}) where {S<:ElementarySpace} = - ProductSpace(tuple(V1, P2.spaces...)) +⊗(V₁::S, V₂::S) where {S<:ElementarySpace}= ProductSpace((V₁, V₂)) +⊗(P1::ProductSpace{S}, V₂::S) where {S<:ElementarySpace} = + ProductSpace(tuple(P1.spaces..., V₂)) +⊗(V₁::S, P2::ProductSpace{S}) where {S<:ElementarySpace} = + ProductSpace(tuple(V₁, P2.spaces...)) ⊗(P1::ProductSpace{S}, P2::ProductSpace{S}) where {S<:ElementarySpace} = ProductSpace(tuple(P1.spaces..., P2.spaces...)) ⊗(P::ProductSpace{S, 0}, ::ProductSpace{S, 0}) where {S<:ElementarySpace} = P diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 8d2fd1e6..f0def932 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -116,19 +116,19 @@ that this is different from `one(V::S)`, which returns the empty product space Base.oneunit(V::ElementarySpace) = oneunit(typeof(V)) """ - ⊕(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S + ⊕(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 `V1`, `V2`, ... Note that all the individual spaces should have the same value for +spaces `V₁`, `V₂`, ... Note that all the individual spaces should have the same value for [`isdual`](@ref), as otherwise the direct sum is not defined. """ function ⊕ end -⊕(V1, V2, V3, V4...) = ⊕(⊕(V1, V2), V3, V4...) +⊕(V₁, V₂, V₃, V₄...) = ⊕(⊕(V₁, V₂), V₃, V₄...) """ - ⊗(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S + ⊗(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S -Create a [`ProductSpace{S}(V1, V2, V3...)`](@ref) representing the tensor product of several +Create a [`ProductSpace{S}(V₁, V₂, V₃...)`](@ref) representing the tensor product of several elementary vector spaces. For convience, Julia's regular multiplication operator `*` applied to vector spaces has the same effect. @@ -136,23 +136,23 @@ The tensor product structure is preserved, see [`fuse`](@ref) for returning a si elementary space of type `S` that is isomorphic to this tensor product. """ function ⊗ end -⊗(V1, V2, V3, V4...) = ⊗(⊗(V1, V2), V3, V4...) +⊗(V₁, V₂, V₃, V₄...) = ⊗(⊗(V₁, V₂), V₃, V₄...) # convenience definitions: -Base.:*(V1::VectorSpace, V2::VectorSpace) = ⊗(V1, V2) +Base.:*(V₁::VectorSpace, V₂::VectorSpace) = ⊗(V₁, V₂) """ - fuse(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S + 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 `V1`, `V2`, ..., or the spaces contained in `P`. +individual spaces `V₁`, `V₂`, ..., or the spaces contained in `P`. """ function fuse end fuse(V::ElementarySpace) = V -fuse(V1::VectorSpace, V2::VectorSpace, V3::VectorSpace...) = - fuse(fuse(fuse(V1), fuse(V2)), V3...) - # calling fuse on V1 and V2 will allow these to be `ProductSpace` +fuse(V₁::VectorSpace, V₂::VectorSpace, V₃::VectorSpace...) = + fuse(fuse(fuse(V₁), fuse(V₂)), V₃...) + # calling fuse on V₁ and V₂ will allow these to be `ProductSpace` """ flip(V::S) where {S<:ElementarySpace} -> S @@ -286,16 +286,16 @@ include("homspace.jl") # Partial order for vector spaces #--------------------------------- """ - isisomorphic(V1::VectorSpace, V2::VectorSpace) - V1 ≅ V2 + isisomorphic(V₁::VectorSpace, V₂::VectorSpace) + V₁ ≅ V₂ -Return if `V1` and `V2` are isomorphic, meaning that there exists isomorphisms from `V1` to -`V2`, i.e. morphisms with left and right inverses. +Return if `V₁` and `V₂` are isomorphic, meaning that there exists isomorphisms from `V₁` to +`V₂`, i.e. morphisms with left and right inverses. """ -function isisomorphic(V1::VectorSpace, V2::VectorSpace) - spacetype(V1) == spacetype(V2) || return false - for c in union(blocksectors(V1), blocksectors(V2)) - if blockdim(V1, c) != blockdim(V2, c) +function isisomorphic(V₁::VectorSpace, V₂::VectorSpace) + spacetype(V₁) == spacetype(V₂) || return false + for c in union(blocksectors(V₁), blocksectors(V₂)) + if blockdim(V₁, c) != blockdim(V₂, c) return false end end @@ -303,16 +303,16 @@ function isisomorphic(V1::VectorSpace, V2::VectorSpace) end """ - ismonomorphic(V1::VectorSpace, V2::VectorSpace) - V1 ≾ V2 + ismonomorphic(V₁::VectorSpace, V₂::VectorSpace) + V₁ ≾ V₂ -Return whether there exist monomorphisms from `V1` to `V2`, i.e. 'injective' morphisms with +Return whether there exist monomorphisms from `V₁` to `V₂`, i.e. 'injective' morphisms with left inverses. """ -function ismonomorphic(V1::VectorSpace, V2::VectorSpace) - spacetype(V1) == spacetype(V2) || return false - for c in blocksectors(V1) - if blockdim(V1, c) > blockdim(V2, c) +function ismonomorphic(V₁::VectorSpace, V₂::VectorSpace) + spacetype(V₁) == spacetype(V₂) || return false + for c in blocksectors(V₁) + if blockdim(V₁, c) > blockdim(V₂, c) return false end end @@ -320,16 +320,16 @@ function ismonomorphic(V1::VectorSpace, V2::VectorSpace) end """ - isepimorphic(V1::VectorSpace, V2::VectorSpace) - V1 ≿ V2 + isepimorphic(V₁::VectorSpace, V₂::VectorSpace) + V₁ ≿ V₂ -Return whether there exist epimorphisms from `V1` to `V2`, i.e. 'surjective' morphisms with +Return whether there exist epimorphisms from `V₁` to `V₂`, i.e. 'surjective' morphisms with right inverses. """ -function isepimorphic(V1::VectorSpace, V2::VectorSpace) - spacetype(V1) == spacetype(V2) || return false - for c in blocksectors(V2) - if blockdim(V1, c) < blockdim(V2, c) +function isepimorphic(V₁::VectorSpace, V₂::VectorSpace) + spacetype(V₁) == spacetype(V₂) || return false + for c in blocksectors(V₂) + if blockdim(V₁, c) < blockdim(V₂, c) return false end end @@ -341,29 +341,25 @@ const ≅ = isisomorphic const ≾ = ismonomorphic const ≿ = isepimorphic -≺(V1::VectorSpace, V2::VectorSpace) = V1 ≾ V2 && !(V1 ≿ V2) -≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2) +≺(V₁::VectorSpace, V₂::VectorSpace) = V₁ ≾ V₂ && !(V₁ ≿ V₂) +≻(V₁::VectorSpace, V₂::VectorSpace) = V₁ ≿ V₂ && !(V₁ ≾ V₂) """ - infimum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) + 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 ≾ V1`, `V ≾ V2`, ... and no other `W ≻ V` has this property. This requires +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. """ -infimum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) = - infimum(infimum(V1, V2), V3...) +infimum(V₁::S, V₂::S, V₃::S...) where S<:ElementarySpace = infimum(infimum(V₁, V₂), V₃...) """ - supremum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) + 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 ≿ V1`, `V ≿ V2`, ... and no other `W ≺ V` has this property. This requires +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. """ -supremum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) = - supremum(supremum(V1, V2), V3...) - -import Base: min, max +supremum(V₁::S, V₂::S, V₃::S...) where S<:ElementarySpace = supremum(supremum(V₁, V₂), V₃...) From 71542b031766e15788df9189762c93eda48f4773 Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 01:56:50 +0200 Subject: [PATCH 15/57] fix TensorKit text in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d20d50a..7d28e367 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Julia package for large-scale tensor computations, with a hint of category the | **Build Status** | **Coverage** | **Quality assurance** | **Downloads** | |:----------------:|:------------:|:---------------------:|:--------------| -| [![CI][ci-img]][ci-url] [![CI (Julia nightly)][ci-julia-nightly-img]][ci-julia-nightly-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [![Strided Downloads][genie-img]][genie-url] | +| [![CI][ci-img]][ci-url] [![CI (Julia nightly)][ci-julia-nightly-img]][ci-julia-nightly-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [![TensorKit Downloads][genie-img]][genie-url] | [github-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI/badge.svg [github-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3ACI From b8441451ef7770c3106f6c7964ca9dee3efcc1ad Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 01:57:26 +0200 Subject: [PATCH 16/57] update exports and remove older deprecations --- src/TensorKit.jl | 9 ++++++--- src/auxiliary/deprecate.jl | 17 ----------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 620631e2..bbadf3f7 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -50,7 +50,7 @@ export fℤ₂, fU₁, fSU₂ export ℤ₂Space, ℤ₃Space, ℤ₄Space, U₁Space, CU₁Space, SU₂Space # tensor maps -export domain, codomain, numind, numout, numin, spacetype, storagetype, eltype +export domain, codomain, numind, numout, numin, spacetype, storagetype, scalartype export domainind, codomainind, allind export tensormaptype export blocksectors, blockdim, block, blocks @@ -61,8 +61,11 @@ export randuniform, randnormal, randisometry, randhaar # special purpose constructors export zero, one, one!, id, isomorphism, unitary, isometry -# tensor algebra and factorizations -export dot, norm, normalize, normalize!, tr +# reexport most of VectorInterface and some more tensor algebra +export zerovector, zerovector!, zerovector!!, scale, scale!, scale!!, add, add!, add!! +export inner, dot, norm, normalize, normalize!, tr + +# factorizations export mul!, lmul!, rmul!, adjoint!, pinv, axpy!, axpby! export leftorth, rightorth, leftnull, rightnull, leftorth!, rightorth!, leftnull!, rightnull!, diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index ce8c100c..c688bbcf 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -1,20 +1,3 @@ -Base.@deprecate min(V1::ElementarySpace, V2::ElementarySpace) infimum(V1, V2) -Base.@deprecate max(V1::ElementarySpace, V2::ElementarySpace) supremum(V1, V2) -Base.@deprecate(infinum(V1, V2...), infimum(V1, V2...)) - -Base.@deprecate(fusiontreetype(::Type{I}, ::StaticLength{N}) where {I<:Sector, N}, - fusiontreetype(I, N)) - -abstract type RepresentationSpace{I<:Sector} end -Base.@deprecate(RepresentationSpace(args...), GradedSpace(args...)) -Base.@deprecate(RepresentationSpace{I}(args...) where {I}, Vect[I](args...)) - -Base.@deprecate(×(a::Sector, b::Sector), ⊠(a,b)) - -Base.@deprecate( - permuteind(t::TensorMap, p1::IndexTuple, p2::IndexTuple=(); copy::Bool = false), - permute(t, p1, p2; copy = copy)) - @noinline function Base.getindex(::ComplexNumbers, I::Type{<:Group}) S = Rep[I] if repr(S) == "GradedSpace[Irrep[$I]]" From 1fea091bb55f8e5db182d1665164430dcb74d6c3 Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 11:42:27 +0200 Subject: [PATCH 17/57] fusion tree symbols into subscripts --- src/fusiontrees/fusiontrees.jl | 18 +- src/fusiontrees/manipulations.jl | 344 +++++++++++++++--------------- src/planar/planaroperations.jl | 12 +- src/sectors/product.jl | 10 +- src/sectors/sectors.jl | 2 +- src/tensors/abstracttensor.jl | 18 +- src/tensors/adjoint.jl | 28 +-- src/tensors/braidingtensor.jl | 198 ++++++++--------- src/tensors/indexmanipulations.jl | 6 +- src/tensors/tensor.jl | 58 ++--- src/tensors/tensoroperations.jl | 36 ++-- src/tensors/tensortreeiterator.jl | 24 +-- 12 files changed, 377 insertions(+), 377 deletions(-) diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index 052e5fd9..10ed9d27 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -113,25 +113,25 @@ function Base.hash(f::FusionTree{I}, h::UInt) where {I} end return h end -function Base.isequal(f1::FusionTree{I, N}, f2::FusionTree{I, N}) where {I<:Sector, N} - f1.coupled == f2.coupled || return false +function Base.isequal(f₁::FusionTree{I, N}, f₂::FusionTree{I, N}) where {I<:Sector, N} + f₁.coupled == f₂.coupled || return false @inbounds for i = 1:N - f1.uncoupled[i] == f2.uncoupled[i] || return false - f1.isdual[i] == f2.isdual[i] || return false + f₁.uncoupled[i] == f₂.uncoupled[i] || return false + f₁.isdual[i] == f₂.isdual[i] || return false end if FusionStyle(I) isa MultipleFusion @inbounds for i=1:N-2 - f1.innerlines[i] == f2.innerlines[i] || return false + f₁.innerlines[i] == f₂.innerlines[i] || return false end end if FusionStyle(I) isa GenericFusion @inbounds for i=1:N-1 - f1.vertices[i] == f2.vertices[i] || return false + f₁.vertices[i] == f₂.vertices[i] || return false end end return true end -Base.isequal(f1::FusionTree, f2::FusionTree) = false +Base.isequal(f₁::FusionTree, f₂::FusionTree) = false # Facilitate getting correct fusion tree types @@ -191,9 +191,9 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,N}) where {I,N} ftail = FusionTree(tailout, f.coupled, isdualout, Base.tail(f.innerlines), Base.tail(f.vertices)) Ctail = convert(A, ftail) - f1 = FusionTree((f.uncoupled[1], f.uncoupled[2]), f.innerlines[1], + f₁ = FusionTree((f.uncoupled[1], f.uncoupled[2]), f.innerlines[1], (f.isdual[1], f.isdual[2]), (), (f.vertices[1],)) - C1 = convert(A, f1) + C1 = convert(A, f₁) dtail = size(Ctail) d1 = size(C1) X = similar(C1, (d1[1], d1[2], Base.tail(dtail)...)) diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index 015b54e9..36b2ee19 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -6,59 +6,59 @@ fusiontreedict(I) = FusionStyle(I) isa UniqueFusion ? SingletonDict : FusionTree # -> only depend on Fsymbol """ - insertat(f::FusionTree{I, N₁}, i::Int, f2::FusionTree{I, N₂}) + insertat(f::FusionTree{I, N₁}, i::Int, f₂::FusionTree{I, N₂}) -> <:AbstractDict{<:FusionTree{I, N₁+N₂-1}, <:Number} -Attach a fusion tree `f2` to the uncoupled leg `i` of the fusion tree `f1` and bring it +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 -`f2.coupled == f1.uncoupled[i]` and `f1.isdual[i] == false`. +`f₂.coupled == f₁.uncoupled[i]` and `f₁.isdual[i] == false`. """ -function insertat(f1::FusionTree{I}, i::Int, f2::FusionTree{I, 0}) where {I} +function insertat(f₁::FusionTree{I}, i::Int, f₂::FusionTree{I, 0}) where {I} # this actually removes uncoupled line i, which should be trivial - (f1.uncoupled[i] == f2.coupled && !f1.isdual[i]) || - throw(SectorMismatch("cannot connect $(f2.uncoupled) to $(f1.uncoupled[i])")) + (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || + throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] - uncoupled = TupleTools.deleteat(f1.uncoupled, i) - coupled = f1.coupled - isdual = TupleTools.deleteat(f1.isdual, i) + uncoupled = TupleTools.deleteat(f₁.uncoupled, i) + coupled = f₁.coupled + isdual = TupleTools.deleteat(f₁.isdual, i) if length(uncoupled) <= 2 inner = () else - inner = TupleTools.deleteat(f1.innerlines, max(1, i-2)) + inner = TupleTools.deleteat(f₁.innerlines, max(1, i-2)) end if length(uncoupled) <= 1 vertices = () else - vertices = TupleTools.deleteat(f1.vertices, max(1, i-1)) + vertices = TupleTools.deleteat(f₁.vertices, max(1, i-1)) end f = FusionTree(uncoupled, coupled, isdual, inner, vertices) return fusiontreedict(I)(f => coeff) end -function insertat(f1::FusionTree{I}, i, f2::FusionTree{I, 1}) where {I} +function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 1}) where {I} # identity operation - (f1.uncoupled[i] == f2.coupled && !f1.isdual[i]) || - throw(SectorMismatch("cannot connect $(f2.uncoupled) to $(f1.uncoupled[i])")) + (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || + throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] - isdual′ = TupleTools.setindex(f1.isdual, f2.isdual[1], i) - f = FusionTree{I}(f1.uncoupled, f1.coupled, isdual′, f1.innerlines, f1.vertices) + isdual′ = TupleTools.setindex(f₁.isdual, f₂.isdual[1], i) + f = FusionTree{I}(f₁.uncoupled, f₁.coupled, isdual′, f₁.innerlines, f₁.vertices) return fusiontreedict(I)(f => coeff) end -function insertat(f1::FusionTree{I}, i, f2::FusionTree{I, 2}) where {I} +function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 2}) where {I} # elementary building block, - (f1.uncoupled[i] == f2.coupled && !f1.isdual[i]) || - throw(SectorMismatch("cannot connect $(f2.uncoupled) to $(f1.uncoupled[i])")) - uncoupled = f1.uncoupled - coupled = f1.coupled - inner = f1.innerlines - b, c = f2.uncoupled - isdual = f1.isdual - isdualb, isdualc = f2.isdual + (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || + throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) + uncoupled = f₁.uncoupled + coupled = f₁.coupled + inner = f₁.innerlines + b, c = f₂.uncoupled + isdual = f₁.isdual + isdualb, isdualc = f₂.isdual if i == 1 uncoupled′ = (b, c, tail(uncoupled)...) isdual′ = (isdualb, isdualc, tail(isdual)...) inner′ = (uncoupled[1], inner...) - vertices′ = (f2.vertices..., f1.vertices...) + vertices′ = (f₂.vertices..., f₁.vertices...) coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] f′ = FusionTree(uncoupled′, coupled, isdual′, inner′, vertices′) return fusiontreedict(I)(f′ => coeff) @@ -85,15 +85,15 @@ function insertat(f1::FusionTree{I}, i, f2::FusionTree{I, 2}) where {I} return newtrees else local newtrees - κ = f2.vertices[1] - λ = f1.vertices[i-1] + κ = f₂.vertices[1] + λ = f₁.vertices[i-1] for e in a ⊗ b inner′ = TupleTools.insertafter(inner, i-2, (e,)) Fmat = Fsymbol(a, b, c, d, e, e′) for μ = 1:size(Fmat, 1), ν = 1:size(Fmat, 2) coeff = conj(Fmat[μ,ν,κ,λ]) iszero(coeff) && continue - vertices′ = TupleTools.setindex(f1.vertices, ν, i-1) + vertices′ = TupleTools.setindex(f₁.vertices, ν, i-1) vertices′ = TupleTools.insertafter(vertices′, i-2, (μ,)) f′ = FusionTree(uncoupled′, coupled, isdual′, inner′, vertices′) if @isdefined newtrees @@ -106,29 +106,29 @@ function insertat(f1::FusionTree{I}, i, f2::FusionTree{I, 2}) where {I} return newtrees end end -function insertat(f1::FusionTree{I,N₁}, i, f2::FusionTree{I,N₂}) where {I,N₁,N₂} +function insertat(f₁::FusionTree{I,N₁}, i, f₂::FusionTree{I,N₂}) where {I,N₁,N₂} F = fusiontreetype(I, N₁ + N₂ - 1) - (f1.uncoupled[i] == f2.coupled && !f1.isdual[i]) || - throw(SectorMismatch("cannot connect $(f2.uncoupled) to $(f1.uncoupled[i])")) + (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || + throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1] T = typeof(coeff) - if length(f1) == 1 - return fusiontreedict(I){F,T}(f2 => coeff) + if length(f₁) == 1 + return fusiontreedict(I){F,T}(f₂ => coeff) end if i == 1 - uncoupled = (f2.uncoupled..., tail(f1.uncoupled)...) - isdual = (f2.isdual..., tail(f1.isdual)...) - inner = (f2.innerlines..., f2.coupled, f1.innerlines...) - vertices = (f2.vertices..., f1.vertices...) - coupled = f1.coupled + uncoupled = (f₂.uncoupled..., tail(f₁.uncoupled)...) + isdual = (f₂.isdual..., tail(f₁.isdual)...) + inner = (f₂.innerlines..., f₂.coupled, f₁.innerlines...) + vertices = (f₂.vertices..., f₁.vertices...) + coupled = f₁.coupled f′ = FusionTree(uncoupled, coupled, isdual, inner, vertices) return fusiontreedict(I){F,T}(f′ => coeff) else # recursive definition - N2 = length(f2) - f2′, f2′′ = split(f2, N2 - 1) + N2 = length(f₂) + f₂′, f₂′′ = split(f₂, N2 - 1) local newtrees::fusiontreedict(I){F,T} - for (f, coeff) in insertat(f1, i, f2′′) - for (f′, coeff′) in insertat(f, i, f2′) + for (f, coeff) in insertat(f₁, i, f₂′′) + for (f′, coeff′) in insertat(f, i, f₂′) if @isdefined newtrees coeff′′ = coeff*coeff′ newtrees[f′] = get(newtrees, f′, zero(coeff′′)) + coeff′′ @@ -151,7 +151,7 @@ internal sector between uncoupled sectors `M` and `M+1` of the original tree `f` 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 -`f1, f2 = split(t, M) ⇒ f == insertat(f2, 1, f1)`. +`f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁)`. """ @inline function split(f::FusionTree{I, N}, M::Int) where {I, N} if M > N || M < 0 @@ -161,20 +161,20 @@ operation is the inverse of `insertat` in the sense that if elseif M === 1 isdual1 = (f.isdual[1],) isdual2 = Base.setindex(f.isdual, false, 1) - f1 = FusionTree{I}((f.uncoupled[1],), f.uncoupled[1], isdual1, (), ()) - f2 = FusionTree{I}(f.uncoupled, f.coupled, isdual2, f.innerlines, f.vertices) - return f1, f2 + f₁ = FusionTree{I}((f.uncoupled[1],), f.uncoupled[1], isdual1, (), ()) + f₂ = FusionTree{I}(f.uncoupled, f.coupled, isdual2, f.innerlines, f.vertices) + return f₁, f₂ elseif M === 0 - f1 = FusionTree{I}((), one(I), (), ()) + f₁ = FusionTree{I}((), one(I), (), ()) uncoupled2 = (one(I), f.uncoupled...) coupled2 = f.coupled isdual2 = (false, f.isdual...) innerlines2 = N >= 2 ? (f.uncoupled[1], f.innerlines...) : () if FusionStyle(I) isa GenericFusion vertices2 = (1, f.vertices...) - return f1, FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2, vertices2) + return f₁, FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2, vertices2) else - return f1, FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2) + return f₁, FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2) end else uncoupled1 = ntuple(n->f.uncoupled[n], M) @@ -193,39 +193,39 @@ operation is the inverse of `insertat` in the sense that if coupled2 = f.coupled vertices2 = ntuple(n->f.vertices[M-1+n], N-M) - f1 = FusionTree{I}(uncoupled1, coupled1, isdual1, innerlines1, vertices1) - f2 = FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2, vertices2) - return f1, f2 + f₁ = FusionTree{I}(uncoupled1, coupled1, isdual1, innerlines1, vertices1) + f₂ = FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2, vertices2) + return f₁, f₂ end end """ - merge(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}, c::I, μ = nothing) + merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = nothing) -> <:AbstractDict{<:FusionTree{I, N₁+N₂}, <:Number} Merge two fusion trees together to a linear combination of fusion trees whose uncoupled -sectors are those of `f1` followed by those of `f2`, and where the two coupled sectors of -`f1` and `f2` are further fused to `c`. In case of +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 `f1` and `f2` to `c` needs to be specified. +the coupled sectors of `f₁` and `f₂` to `c` needs to be specified. """ -function merge(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}, +function merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, c::I, μ = nothing) where {I, N₁, N₂} if FusionStyle(I) isa GenericFusion && μ === nothing throw(ArgumentError("vertex label for merging required")) end - if !(c in f1.coupled ⊗ f2.coupled) - throw(SectorMismatch("cannot fuse sectors $(f1.coupled) and $(f2.coupled) to $c")) + if !(c in f₁.coupled ⊗ f₂.coupled) + throw(SectorMismatch("cannot fuse sectors $(f₁.coupled) and $(f₂.coupled) to $c")) end - f0 = FusionTree((f1.coupled, f2.coupled), c, (false, false), (), (μ,)) - f, coeff = first(insertat(f0, 1, f1)) # takes fast path, single output + f₀ = FusionTree((f₁.coupled, f₂.coupled), c, (false, false), (), (μ,)) + f, coeff = first(insertat(f₀, 1, f₁)) # takes fast path, single output @assert coeff == one(coeff) - return insertat(f, N₁+1, f2) + return insertat(f, N₁+1, f₂) end -function merge(f1::FusionTree{I, 0}, f2::FusionTree{I, 0}, c::I, μ = nothing) where {I} +function merge(f₁::FusionTree{I, 0}, f₂::FusionTree{I, 0}, c::I, μ = nothing) where {I} c == one(I) || - throw(SectorMismatch("cannot fuse sectors $(f1.coupled) and $(f2.coupled) to $c")) - return fusiontreedict(I)(f1=>Fsymbol(c, c, c, c, c, c)[1,1,1,1]) + throw(SectorMismatch("cannot fuse sectors $(f₁.coupled) and $(f₂.coupled) to $c")) + return fusiontreedict(I)(f₁=>Fsymbol(c, c, c, c, c, c)[1,1,1,1]) end # ELEMENTARY DUALITY MANIPULATIONS: A- and B-moves @@ -236,77 +236,77 @@ end # -> A-move (foldleft, foldright) is complicated, needs to be reexpressed in standard form # change to N₁ - 1, N₂ + 1 -function bendright(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} +function bendright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} # map final splitting vertex (a, b)<-c to fusion vertex a<-(c, dual(b)) @assert N₁ > 0 - c = f1.coupled - a = N₁ == 1 ? one(I) : (N₁ == 2 ? f1.uncoupled[1] : f1.innerlines[end]) - b = f1.uncoupled[N₁] + c = f₁.coupled + a = N₁ == 1 ? one(I) : (N₁ == 2 ? f₁.uncoupled[1] : f₁.innerlines[end]) + b = f₁.uncoupled[N₁] - uncoupled1 = Base.front(f1.uncoupled) - isdual1 = Base.front(f1.isdual) - inner1 = N₁ > 2 ? Base.front(f1.innerlines) : () - vertices1 = N₁ > 1 ? Base.front(f1.vertices) : () - f1′ = FusionTree(uncoupled1, a, isdual1, inner1, vertices1) + uncoupled1 = Base.front(f₁.uncoupled) + isdual1 = Base.front(f₁.isdual) + inner1 = N₁ > 2 ? Base.front(f₁.innerlines) : () + vertices1 = N₁ > 1 ? Base.front(f₁.vertices) : () + f₁′ = FusionTree(uncoupled1, a, isdual1, inner1, vertices1) - uncoupled2 = (f2.uncoupled..., dual(b)) - isdual2 = (f2.isdual..., !(f1.isdual[N₁])) - inner2 = N₂ > 1 ? (f2.innerlines..., c) : () + uncoupled2 = (f₂.uncoupled..., dual(b)) + isdual2 = (f₂.isdual..., !(f₁.isdual[N₁])) + inner2 = N₂ > 1 ? (f₂.innerlines..., c) : () if FusionStyle(I) isa MultiplicityFreeFusion coeff = sqrtdim(c) * isqrtdim(a) * Bsymbol(a, b, c) - if f1.isdual[N₁] + if f₁.isdual[N₁] coeff *= conj(frobeniusschur(dual(b))) end - vertices2 = N₂ > 0 ? (f2.vertices..., nothing) : () - f2′ = FusionTree(uncoupled2, a, isdual2, inner2, vertices2) - return SingletonDict( (f1′, f2′) => coeff ) + vertices2 = N₂ > 0 ? (f₂.vertices..., nothing) : () + f₂′ = FusionTree(uncoupled2, a, isdual2, inner2, vertices2) + return SingletonDict( (f₁′, f₂′) => coeff ) else local newtrees Bmat = Bsymbol(a, b, c) - μ = N₁ > 1 ? f1.vertices[end] : 1 + μ = N₁ > 1 ? f₁.vertices[end] : 1 for ν = 1:size(Bmat, 2) coeff = sqrtdim(c) * isqrtdim(a) * Bmat[μ,ν] iszero(coeff) && continue - if f1.isdual[N₁] + if f₁.isdual[N₁] coeff *= conj(frobeniusschur(dual(b))) end - vertices2 = N₂ > 0 ? (f2.vertices..., ν) : () - f2′ = FusionTree(uncoupled2, a, isdual2, inner2, vertices2) + vertices2 = N₂ > 0 ? (f₂.vertices..., ν) : () + f₂′ = FusionTree(uncoupled2, a, isdual2, inner2, vertices2) if @isdefined newtrees - push!(newtrees, (f1′, f2′) => coeff) + push!(newtrees, (f₁′, f₂′) => coeff) else - newtrees = FusionTreeDict( (f1′, f2′) => coeff ) + newtrees = FusionTreeDict( (f₁′, f₂′) => coeff ) end end return newtrees end end # change to N₁ + 1, N₂ - 1 -function bendleft(f1::FusionTree{I}, f2::FusionTree{I}) where I +function bendleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where I # map final fusion vertex c<-(a, b) to splitting vertex (c, dual(b))<-a - return fusiontreedict(I)((f1′, f2′) => conj(coeff) for - ((f2′, f1′), coeff) in bendright(f2, f1)) + return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) for + ((f₂′, f₁′), coeff) in bendright(f₂, f₁)) end # change to N₁ - 1, N₂ + 1 -function foldright(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} +function foldright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} # map first splitting vertex (a, b)<-c to fusion vertex b<-(dual(a), c) @assert N₁ > 0 - a = f1.uncoupled[1] - isduala = f1.isdual[1] + a = f₁.uncoupled[1] + isduala = f₁.isdual[1] factor = sqrtdim(a) if !isduala factor *= frobeniusschur(a) end c1 = dual(a) - c2 = f1.coupled - uncoupled = Base.tail(f1.uncoupled) - isdual = Base.tail(f1.isdual) + c2 = f₁.coupled + uncoupled = Base.tail(f₁.uncoupled) + isdual = Base.tail(f₁.isdual) if FusionStyle(I) isa UniqueFusion c = first(c1 ⊗ c2) - fl = FusionTree{I}(Base.tail(f1.uncoupled), c, Base.tail(f1.isdual)) - fr = FusionTree{I}((c1, f2.uncoupled...), c, (!isduala, f2.isdual...)) + fl = FusionTree{I}(Base.tail(f₁.uncoupled), c, Base.tail(f₁.isdual)) + fr = FusionTree{I}((c1, f₂.uncoupled...), c, (!isduala, f₂.isdual...)) return fusiontreedict(I)((fl, fr) => factor) else hasmultiplicities = FusionStyle(a) isa GenericFusion @@ -314,15 +314,15 @@ function foldright(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {I<:S if N₁ == 1 cset = (one(c1),) elseif N₁ == 2 - cset = (f1.uncoupled[2],) + cset = (f₁.uncoupled[2],) else - cset = ⊗(Base.tail(f1.uncoupled)...) + cset = ⊗(Base.tail(f₁.uncoupled)...) end for c in c1 ⊗ c2 c ∈ cset || continue for μ in (hasmultiplicities ? (1:Nsymbol(c1, c2, c)) : (nothing,)) fc = FusionTree((c1, c2), c, (!isduala, false), (), (μ,)) - for (fl′, coeff1) in insertat(fc, 2, f1) + for (fl′, coeff1) in insertat(fc, 2, f₁) N₁ > 1 && fl′.innerlines[1] != one(I) && continue coupled = fl′.coupled uncoupled = Base.tail(Base.tail(fl′.uncoupled)) @@ -330,7 +330,7 @@ function foldright(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {I<:S inner = N₁ <= 3 ? () : Base.tail(Base.tail(fl′.innerlines)) vertices = N₁ <= 2 ? () : Base.tail(Base.tail(fl′.vertices)) fl = FusionTree{I}(uncoupled, coupled, isdual, inner, vertices) - for (fr, coeff2) in insertat(fc, 2, f2) + for (fr, coeff2) in insertat(fc, 2, f₂) coeff = factor * coeff1 * coeff2 if (@isdefined newtrees) newtrees[(fl,fr)] = get(newtrees, (fl, fr), zero(coeff)) + coeff @@ -345,10 +345,10 @@ function foldright(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {I<:S end end # change to N₁ + 1, N₂ - 1 -function foldleft(f1::FusionTree{I}, f2::FusionTree{I}) where I +function foldleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where I # map first fusion vertex c<-(a, b) to splitting vertex (dual(a), c)<-b - return fusiontreedict(I)((f1′, f2′) => conj(coeff) for - ((f2′, f1′), coeff) in foldright(f2, f1)) + return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) for + ((f₂′, f₁′), coeff) in foldright(f₂, f₁)) end @@ -372,10 +372,10 @@ function iscyclicpermutation(v1, v2) end # clockwise cyclic permutation while preserving (N₁, N₂): foldright & bendleft -function cycleclockwise(f1::FusionTree{I}, f2::FusionTree{I}) where {I<:Sector} +function cycleclockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<:Sector} local newtrees - if length(f1) > 0 - for ((f1a, f2a), coeffa) in foldright(f1, f2) + if length(f₁) > 0 + for ((f1a, f2a), coeffa) in foldright(f₁, f₂) for ((f1b, f2b), coeffb) in bendleft(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) @@ -386,7 +386,7 @@ function cycleclockwise(f1::FusionTree{I}, f2::FusionTree{I}) where {I<:Sector} end end else - for ((f1a, f2a), coeffa) in bendleft(f1, f2) + for ((f1a, f2a), coeffa) in bendleft(f₁, f₂) for ((f1b, f2b), coeffb) in foldright(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) @@ -401,10 +401,10 @@ function cycleclockwise(f1::FusionTree{I}, f2::FusionTree{I}) where {I<:Sector} end # anticlockwise cyclic permutation while preserving (N₁, N₂): foldleft & bendright -function cycleanticlockwise(f1::FusionTree{I}, f2::FusionTree{I}) where {I<:Sector} +function cycleanticlockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<:Sector} local newtrees - if length(f2) > 0 - for ((f1a, f2a), coeffa) in foldleft(f1, f2) + if length(f₂) > 0 + for ((f1a, f2a), coeffa) in foldleft(f₁, f₂) for ((f1b, f2b), coeffb) in bendright(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) @@ -415,7 +415,7 @@ function cycleanticlockwise(f1::FusionTree{I}, f2::FusionTree{I}) where {I<:Sect end end else - for ((f1a, f2a), coeffa) in bendright(f1, f2) + for ((f1a, f2a), coeffa) in bendright(f₁, f₂) for ((f1b, f2b), coeffb) in foldleft(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) @@ -431,45 +431,45 @@ end # repartition double fusion tree """ - repartition(f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}, N::Int) where {I, N₁, N₂} + 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 (`f1`) and incoming sectors (`f2`) respectively (with identical coupled sector -`f1.coupled == f2.coupled`). Computes new trees and corresponding coefficients obtained from +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. """ -@inline function repartition(f1::FusionTree{I, N₁}, - f2::FusionTree{I, N₂}, +@inline function repartition(f₁::FusionTree{I, N₁}, + f₂::FusionTree{I, N₂}, N::Int) where {I<:Sector, N₁, N₂} - f1.coupled == f2.coupled || throw(SectorMismatch()) + f₁.coupled == f₂.coupled || throw(SectorMismatch()) @assert 0 <= N <= N₁+N₂ - return _recursive_repartition(f1, f2, Val(N)) + return _recursive_repartition(f₁, f₂, Val(N)) end -function _recursive_repartition(f1::FusionTree{I, N₁}, - f2::FusionTree{I, N₂}, +function _recursive_repartition(f₁::FusionTree{I, N₁}, + f₂::FusionTree{I, N₂}, ::Val{N}) where {I<:Sector, N₁, N₂, N} # recursive definition is only way to get correct number of loops for # GenericFusion, but is too complex for type inference to handle, so we # precompute the parameters of the return type - F1 = fusiontreetype(I, N) - F2 = fusiontreetype(I, N₁ + N₂ - N) + F₁ = fusiontreetype(I, N) + F₂ = fusiontreetype(I, N₁ + N₂ - N) coeff = @inbounds Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] T = typeof(coeff) if N == N₁ - return fusiontreedict(I){Tuple{F1, F2}, T}( (f1, f2) => coeff) + return fusiontreedict(I){Tuple{F₁, F₂}, T}( (f₁, f₂) => coeff) else - local newtrees::fusiontreedict(I){Tuple{F1, F2}, T} - for ((f1′, f2′), coeff1) in (N < N₁ ? bendright(f1, f2) : bendleft(f1, f2)) - for ((f1′′, f2′′), coeff2) in _recursive_repartition(f1′, f2′, Val(N)) + local newtrees::fusiontreedict(I){Tuple{F₁, F₂}, T} + for ((f₁′, f₂′), coeff1) in (N < N₁ ? bendright(f₁, f₂) : bendleft(f₁, f₂)) + for ((f₁′′, f₂′′), coeff2) in _recursive_repartition(f₁′, f₂′, Val(N)) if (@isdefined newtrees) - push!(newtrees, (f1′′, f2′′) => coeff1*coeff2) + push!(newtrees, (f₁′′, f₂′′) => coeff1*coeff2) else newtrees = - fusiontreedict(I){Tuple{F1, F2}, T}((f1′′, f2′′) => coeff1*coeff2) + fusiontreedict(I){Tuple{F₁, F₂}, T}((f₁′′, f₂′′) => coeff1*coeff2) end end end @@ -482,7 +482,7 @@ const transposecache = LRU{Any, Any}(; maxsize = 10^5) const usetransposecache = Ref{Bool}(true) """ - transpose(f1::FusionTree{I}, f2::FusionTree{I}, + 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} @@ -493,11 +493,11 @@ outgoing (`t1`) and incoming sectors (`t2`) respectively (with identical coupled repartitioning and permuting the tree such that sectors `p1` become outgoing and sectors `p2` become incoming. """ -function Base.transpose(f1::FusionTree{I}, f2::FusionTree{I}, +function Base.transpose(f₁::FusionTree{I}, f₂::FusionTree{I}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} N = N₁ + N₂ - @assert length(f1) + length(f2) == N - p = linearizepermutation(p1, p2, length(f1), length(f2)) + @assert length(f₁) + length(f₂) == N + p = linearizepermutation(p1, p2, length(f₁), length(f₂)) @assert iscyclicpermutation(p) if usetransposecache[] u = one(I) @@ -505,9 +505,9 @@ function Base.transpose(f1::FusionTree{I}, f2::FusionTree{I}, F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) D = fusiontreedict(I){Tuple{F₁, F₂}, T} - return _get_transpose(D, (f1, f2, p1, p2)) + return _get_transpose(D, (f₁, f₂, p1, p2)) else - return _transpose((f1, f2, p1, p2)) + return _transpose((f₁, f₂, p1, p2)) end end @@ -521,10 +521,10 @@ end const TransposeKey{I<:Sector, N₁, N₂} = Tuple{<:FusionTree{I}, <:FusionTree{I}, IndexTuple{N₁}, IndexTuple{N₂}} -function _transpose((f1, f2, p1, p2)::TransposeKey{I,N₁,N₂}) where {I<:Sector, N₁, N₂} +function _transpose((f₁, f₂, p1, p2)::TransposeKey{I,N₁,N₂}) where {I<:Sector, N₁, N₂} N = N₁ + N₂ - p = linearizepermutation(p1, p2, length(f1), length(f2)) - newtrees = repartition(f1, f2, N₁) + p = linearizepermutation(p1, p2, length(f₁), length(f₂)) + newtrees = repartition(f₁, f₂, N₁) length(p) == 0 && return newtrees i1 = findfirst(==(1), p) @assert i1 !== nothing @@ -568,18 +568,18 @@ end # -> composite manipulations that depend on the duality (rigidity) and pivotal structure # -> planar manipulations that do not require braiding, everything is in Fsymbol (A/Bsymbol) -function planar_trace(f1::FusionTree{I}, f2::FusionTree{I}, +function planar_trace(f₁::FusionTree{I}, f₂::FusionTree{I}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector, N₁, N₂, N₃} N = N₁ + N₂ + 2N₃ - @assert length(f1) + length(f2) == N + @assert length(f₁) + length(f₂) == N if N₃ == 0 - return transpose(f1, f2, p1, p2) + return transpose(f₁, f₂, p1, p2) end - linearindex = (ntuple(identity, Val(length(f1)))..., - reverse(length(f1) .+ ntuple(identity, Val(length(f2))))...) + linearindex = (ntuple(identity, Val(length(f₁)))..., + reverse(length(f₁) .+ ntuple(identity, Val(length(f₂))))...) q1′ = TupleTools.getindices(linearindex, q1) @@ -594,9 +594,9 @@ function planar_trace(f1::FusionTree{I}, f2::FusionTree{I}, F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) newtrees = FusionTreeDict{Tuple{F₁,F₂}, T}() - for ((f1′, f2′), coeff′) in repartition(f1, f2, N) - for (f1′′, coeff′′) in planar_trace(f1′, q1′, q2′) - for (f12′′′, coeff′′′) in transpose(f1′′, f2′, p1′, p2′) + for ((f₁′, f₂′), coeff′) in repartition(f₁, f₂, N) + for (f₁′′, coeff′′) in planar_trace(f₁′, q1′, q2′) + for (f12′′′, coeff′′′) in transpose(f₁′′, f₂′, p1′, p2′) coeff = coeff′ * coeff′′ * coeff′′′ if !iszero(coeff) newtrees[f12′′′] = get(newtrees, f12′′′, zero(coeff)) + coeff @@ -961,40 +961,40 @@ const usebraidcache_abelian = Ref{Bool}(false) const usebraidcache_nonabelian = Ref{Bool}(true) """ - braid(f1::FusionTree{I}, f2::FusionTree{I}, + 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 `f1` and fusion tree `f2`, such that the incoming sectors `f2.uncoupled` are fused to -`f1.coupled == f2.coupled` and then to the outgoing sectors `f1.uncoupled`. Compute new +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 `f1` and fusion tree `f2` have levels (or depths) `levels1` and `levels2` +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. """ -function braid(f1::FusionTree{I}, f2::FusionTree{I}, +function braid(f₁::FusionTree{I}, f₂::FusionTree{I}, levels1::IndexTuple, levels2::IndexTuple, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} - @assert length(f1) + length(f2) == N₁ + N₂ - @assert length(f1) == length(levels1) && length(f2) == length(levels2) + @assert length(f₁) + length(f₂) == N₁ + N₂ + @assert length(f₁) == length(levels1) && length(f₂) == length(levels2) @assert TupleTools.isperm((p1..., p2...)) - if FusionStyle(f1) isa UniqueFusion && - BraidingStyle(f1) isa SymmetricBraiding + if FusionStyle(f₁) isa UniqueFusion && + BraidingStyle(f₁) isa SymmetricBraiding if usebraidcache_abelian[] u = one(I) T = Int F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) D = SingletonDict{Tuple{F₁, F₂}, T} - return _get_braid(D, (f1, f2, levels1, levels2, p1, p2)) + return _get_braid(D, (f₁, f₂, levels1, levels2, p1, p2)) else - return _braid((f1, f2, levels1, levels2, p1, p2)) + return _braid((f₁, f₂, levels1, levels2, p1, p2)) end else if usebraidcache_nonabelian[] @@ -1003,9 +1003,9 @@ function braid(f1::FusionTree{I}, f2::FusionTree{I}, F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) D = FusionTreeDict{Tuple{F₁, F₂}, T} - return _get_braid(D, (f1, f2, levels1, levels2, p1, p2)) + return _get_braid(D, (f₁, f₂, levels1, levels2, p1, p2)) else - return _braid((f1, f2, levels1, levels2, p1, p2)) + return _braid((f₁, f₂, levels1, levels2, p1, p2)) end end end @@ -1021,18 +1021,18 @@ const BraidKey{I<:Sector, N₁, N₂} = Tuple{<:FusionTree{I}, <:FusionTree{I}, IndexTuple, IndexTuple, IndexTuple{N₁}, IndexTuple{N₂}} -function _braid((f1, f2, l1, l2, p1, p2)::BraidKey{I, N₁, N₂}) where {I<:Sector, N₁, N₂} - p = linearizepermutation(p1, p2, length(f1), length(f2)) +function _braid((f₁, f₂, l1, l2, p1, p2)::BraidKey{I, N₁, N₂}) where {I<:Sector, N₁, N₂} + p = linearizepermutation(p1, p2, length(f₁), length(f₂)) levels = (l1..., reverse(l2)...) local newtrees - for ((f, f0), coeff1) in repartition(f1, f2, N₁ + N₂) + for ((f, f0), coeff1) in repartition(f₁, f₂, N₁ + N₂) for (f′, coeff2) in braid(f, levels, p) - for ((f1′, f2′), coeff3) in repartition(f′, f0, N₁) + for ((f₁′, f₂′), coeff3) in repartition(f′, f0, N₁) if @isdefined newtrees - newtrees[(f1′, f2′)] = get(newtrees, (f1′, f2′), zero(coeff3)) + + newtrees[(f₁′, f₂′)] = get(newtrees, (f₁′, f₂′), zero(coeff3)) + coeff1*coeff2*coeff3 else - newtrees = fusiontreedict(I)( (f1′, f2′) => coeff1*coeff2*coeff3 ) + newtrees = fusiontreedict(I)( (f₁′, f₂′) => coeff1*coeff2*coeff3 ) end end end @@ -1041,7 +1041,7 @@ function _braid((f1, f2, l1, l2, p1, p2)::BraidKey{I, N₁, N₂}) where {I<:Sec end """ - permute(f1::FusionTree{I}, f2::FusionTree{I}, + 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} @@ -1052,10 +1052,10 @@ outgoing (`t1`) and incoming sectors (`t2`) respectively (with identical coupled repartitioning and permuting the tree such that sectors `p1` become outgoing and sectors `p2` become incoming. """ -function permute(f1::FusionTree{I}, f2::FusionTree{I}, +function permute(f₁::FusionTree{I}, f₂::FusionTree{I}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} @assert BraidingStyle(I) isa SymmetricBraiding - levels1 = ntuple(identity, length(f1)) - levels2 = length(f1) .+ ntuple(identity, length(f2)) - return braid(f1, f2, levels1, levels2, p1, p2) + levels1 = ntuple(identity, length(f₁)) + levels2 = length(f₁) .+ ntuple(identity, length(f₂)) + return braid(f₁, f₂, levels1, levels2, p1, p2) end diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index 8744ea3f..e26dab22 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -30,9 +30,9 @@ function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N end pdata = linearize(pC) - for (f1, f2) in fusiontrees(A) - for ((f1′, f2′), coeff) in planar_trace(f1, f2, pC..., pA...) - TO._trace!(α * coeff, A[f1, f2], true, C[f1′, f2′], pdata, pA...) + for (f₁, f₂) in fusiontrees(A) + for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, pC..., pA...) + TO._trace!(α * coeff, A[f₁, f₂], true, C[f₁′, f₂′], pdata, pA...) end end return C @@ -94,9 +94,9 @@ end # rmul!(tdst, β) # end # pdata = (p1..., p2...) -# for (f1, f2) in fusiontrees(tsrc) -# for ((f1′, f2′), coeff) in planar_trace(f1, f2, p1, p2, q1, q2) -# TO._trace!(α * coeff, tsrc[f1, f2], true, tdst[f1′, f2′], pdata, q1, q2) +# for (f₁, f₂) in fusiontrees(tsrc) +# for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, p1, p2, q1, q2) +# TO._trace!(α * coeff, tsrc[f₁, f₂], true, tdst[f₁′, f₂′], pdata, q1, q2) # end # end # return tdst diff --git a/src/sectors/product.jl b/src/sectors/product.jl index 1768bf9c..178560f7 100644 --- a/src/sectors/product.jl +++ b/src/sectors/product.jl @@ -59,12 +59,12 @@ _tailsector(x::ProductSector) = ProductSector(tail(x.sectors)) function Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P<:ProductSector} heads = map(_firstsector, (a, b, c, d, e, f)) tails = map(_tailsector, (a, b, c, d, e, f)) - f1 = Fsymbol(heads...) - f2 = Fsymbol(tails...) - if f1 isa Number || f2 isa Number - f1 * f2 + f₁ = Fsymbol(heads...) + f₂ = Fsymbol(tails...) + if f₁ isa Number || f₂ isa Number + f₁ * f₂ else - _kron(f1, f2) + _kron(f₁, f₂) end end Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P<:ProductSector{<:Tuple{Sector}}} = diff --git a/src/sectors/sectors.jl b/src/sectors/sectors.jl index 42bb6bdd..62489894 100644 --- a/src/sectors/sectors.jl +++ b/src/sectors/sectors.jl @@ -242,7 +242,7 @@ vertex_labeltype(I::Type{<:Sector}) = typeof(vertex_ind2label(1, one(I), one(I), # combine fusion properties of tensor products of sectors Base.:&(f::F, ::F) where {F<:FusionStyle} = f -Base.:&(f1::FusionStyle, f2::FusionStyle) = f2 & f1 +Base.:&(f₁::FusionStyle, f₂::FusionStyle) = f₂ & f₁ Base.:&(::SimpleFusion, ::UniqueFusion) = SimpleFusion() Base.:&(::GenericFusion, ::UniqueFusion) = GenericFusion() diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index a16d966d..d310ac40 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -127,15 +127,15 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap{S,N₁,N₂}) where {S cod = codomain(t) dom = domain(t) local A - for (f1, f2) in fusiontrees(t) - F1 = convert(Array, f1) - F2 = convert(Array, f2) - sz1 = size(F1) - sz2 = size(F2) + for (f₁, f₂) in fusiontrees(t) + F₁ = convert(Array, f₁) + F₂ = convert(Array, f₂) + sz1 = size(F₁) + sz2 = size(F₂) d1 = TupleTools.front(sz1) d2 = TupleTools.front(sz2) - F = reshape(reshape(F1, TupleTools.prod(d1), sz1[end]) * - reshape(F2, TupleTools.prod(d2), sz2[end])', (d1..., d2...)) + F = reshape(reshape(F₁, TupleTools.prod(d1), sz1[end]) * + reshape(F₂, TupleTools.prod(d2), sz2[end])', (d1..., d2...)) if !(@isdefined A) if eltype(F) <: Complex T = complex(float(eltype(t))) @@ -146,8 +146,8 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap{S,N₁,N₂}) where {S end A = fill(zero(T), (dims(cod)..., dims(dom)...)) end - Aslice = StridedView(A)[axes(cod, f1.uncoupled)..., axes(dom, f2.uncoupled)...] - axpy!(1, StridedView(_kron(convert(Array, t[f1, f2]), F)), Aslice) + Aslice = StridedView(A)[axes(cod, f₁.uncoupled)..., axes(dom, f₂.uncoupled)...] + axpy!(1, StridedView(_kron(convert(Array, t[f₁, f₂]), F)), Aslice) end return A end diff --git a/src/tensors/adjoint.jl b/src/tensors/adjoint.jl index af19a543..a9c041f2 100644 --- a/src/tensors/adjoint.jl +++ b/src/tensors/adjoint.jl @@ -39,19 +39,19 @@ fusiontrees(::AdjointTrivialTensorMap) = ((nothing, nothing),) fusiontrees(t::AdjointTensorMap) = TensorKeyIterator(t.parent.colr, t.parent.rowr) function Base.getindex(t::AdjointTensorMap{S, N₁, N₂, I}, - f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {S, N₁, N₂, I} - c = f1.coupled + f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {S, N₁, N₂, I} + c = f₁.coupled @boundscheck begin - c == f2.coupled || throw(SectorMismatch()) - hassector(codomain(t), f1.uncoupled) && hassector(domain(t), f2.uncoupled) + c == f₂.coupled || throw(SectorMismatch()) + hassector(codomain(t), f₁.uncoupled) && hassector(domain(t), f₂.uncoupled) end return sreshape( - (StridedView(t.parent.data[c])[t.parent.rowr[c][f2], t.parent.colr[c][f1]])', - (dims(codomain(t), f1.uncoupled)..., dims(domain(t), f2.uncoupled)...)) + (StridedView(t.parent.data[c])[t.parent.rowr[c][f₂], t.parent.colr[c][f₁]])', + (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...)) end @propagate_inbounds Base.setindex!(t::AdjointTensorMap{S, N₁, N₂}, v, - f1::FusionTree{I, N₁}, f2::FusionTree{I, N₂}) where {S, N₁, N₂, I} = - copy!(getindex(t, f1, f2), v) + f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {S, N₁, N₂, I} = + copy!(getindex(t, f₁, f₂), v) @inline Base.getindex(t::AdjointTrivialTensorMap) = sreshape(StridedView(t.parent.data)', (dims(codomain(t))..., dims(domain(t))...)) @@ -90,15 +90,15 @@ function Base.show(io::IO, t::AdjointTensorMap{S}) where {S<:IndexSpace} Base.print_array(io, t[]) println(io) elseif FusionStyle(sectortype(S)) isa UniqueFusion - for (f1, f2) in fusiontrees(t) - println(io, "* Data for sector ", f1.uncoupled, " ← ", f2.uncoupled, ":") - Base.print_array(io, t[f1, f2]) + for (f₁, f₂) in fusiontrees(t) + println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") + Base.print_array(io, t[f₁, f₂]) println(io) end else - for (f1, f2) in fusiontrees(t) - println(io, "* Data for fusiontree ", f1, " ← ", f2, ":") - Base.print_array(io, t[f1, f2]) + for (f₁, f₂) in fusiontrees(t) + println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") + Base.print_array(io, t[f₁, f₂]) println(io) end end diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 518c171e..a443edd7 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -49,18 +49,18 @@ function fusiontrees(b::BraidingTensor) colrc = FusionTreeDict{F, UnitRange{Int}}() offset1 = 0 for s1 in sectors(codom) - for f1 in fusiontrees(s1, c, map(isdual, codom.spaces)) + for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) r = (offset1 + 1):(offset1 + dim(codom, s1)) - push!(rowrc, f1 => r) + push!(rowrc, f₁ => r) offset1 = last(r) end end dim1 = offset1 offset2 = 0 for s2 in sectors(dom) - for f2 in fusiontrees(s2, c, map(isdual, dom.spaces)) + for f₂ in fusiontrees(s2, c, map(isdual, dom.spaces)) r = (offset2 + 1):(offset2 + dim(dom, s2)) - push!(colrc, f2 => r) + push!(colrc, f₂ => r) offset2 = last(r) end end @@ -78,24 +78,24 @@ function Base.getindex(b::BraidingTensor{S}) where S return sreshape(StridedView(block(b, Trivial())), d) end -@inline function Base.getindex(b::BraidingTensor, f1::FusionTree{I,2}, f2::FusionTree{I,2}) where {I<:Sector} +@inline function Base.getindex(b::BraidingTensor, f₁::FusionTree{I,2}, f₂::FusionTree{I,2}) where {I<:Sector} I == sectortype(b) || throw(SectorMismatch()) - c = f1.coupled + c = f₁.coupled V1, V2 = domain(b) @boundscheck begin - c == f2.coupled || throw(SectorMismatch()) - ((f1.uncoupled[1] ∈ sectors(V2)) && (f2.uncoupled[1] ∈ sectors(V1))) || throw(SectorMismatch()) - ((f1.uncoupled[2] ∈ sectors(V1)) && (f2.uncoupled[2] ∈ sectors(V2))) || throw(SectorMismatch()) + c == f₂.coupled || throw(SectorMismatch()) + ((f₁.uncoupled[1] ∈ sectors(V2)) && (f₂.uncoupled[1] ∈ sectors(V1))) || throw(SectorMismatch()) + ((f₁.uncoupled[2] ∈ sectors(V1)) && (f₂.uncoupled[2] ∈ sectors(V2))) || throw(SectorMismatch()) end @inbounds begin - d = (dims(V2 ⊗ V1, f1.uncoupled)..., dims(V1 ⊗ V2, f2.uncoupled)...) + d = (dims(V2 ⊗ V1, f₁.uncoupled)..., dims(V1 ⊗ V2, f₂.uncoupled)...) n1 = d[1]*d[2] n2 = d[3]*d[4] data = fill!(storagetype(b)(undef, (n1, n2)), zero(eltype(b))) - a1, a2 = f2.uncoupled - if f1.uncoupled == (a2, a1) - braiddict = artin_braid(f2, 1; inv = b.adjoint) - r = get(braiddict, f1, zero(valtype(braiddict))) + a1, a2 = f₂.uncoupled + if f₁.uncoupled == (a2, a1) + braiddict = artin_braid(f₂, 1; inv = b.adjoint) + r = get(braiddict, f₁, zero(valtype(braiddict))) si = 1 + d[1]*d[2]*d[3] sj = d[1] + d[1]*d[2] @inbounds for i = 1:d[1], j = 1:d[2] @@ -110,16 +110,16 @@ Base.copy(b::BraidingTensor) = copy!(similar(b), b) function Base.copy!(t::TensorMap, b::BraidingTensor) space(t) == space(b) || throw(SectorMismatch()) fill!(t, zero(eltype(t))) - for (f1, f2) in fusiontrees(t) - data = t[f1, f2] + for (f₁, f₂) in fusiontrees(t) + data = t[f₁, f₂] if sectortype(t) == Trivial r = one(eltype(t)) else - a1, a2 = f2.uncoupled - c = f2.coupled - f1.uncoupled == (a2, a1) || continue - braiddict = artin_braid(f2, 1; inv = b.adjoint) - r = convert(eltype(t), get(braiddict, f1, zero(valtype(braiddict)))) + a1, a2 = f₂.uncoupled + c = f₂.coupled + f₁.uncoupled == (a2, a1) || continue + braiddict = artin_braid(f₂, 1; inv = b.adjoint) + r = convert(eltype(t), get(braiddict, f₁, zero(valtype(braiddict)))) end for i = 1:size(data, 1), j = 1:size(data, 2) data[i, j, j, i] = r @@ -147,14 +147,14 @@ function block(b::BraidingTensor, s::Sector) n = blockdim(domain(b), s) data = fill!(storagetype(b)(undef, (n, n)), zero(eltype(b))) iter = fusiontrees(b) # actually contains information about ranges as well - for (f2, r2) in iter.colr[s] - for (f1, r1) in iter.rowr[s] - a1, a2 = f2.uncoupled + for (f₂, r2) in iter.colr[s] + for (f₁, r1) in iter.rowr[s] + a1, a2 = f₂.uncoupled d1 = dim(V1, a1) d2 = dim(V2, a2) - f1.uncoupled == (a2, a1) || continue - braiddict = artin_braid(f2, 1; inv = b.adjoint) - r = convert(eltype(b), get(braiddict, f1, zero(valtype(braiddict)))) + f₁.uncoupled == (a2, a1) || continue + braiddict = artin_braid(f₂, 1; inv = b.adjoint) + r = convert(eltype(b), get(braiddict, f₁, zero(valtype(braiddict)))) si = 1 + n*d1 sj = d2 + n start = first(r1) + (first(r2)-1) * n @@ -194,12 +194,12 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, end braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindA[1]] > braidingtensor_levels[cindA[2]] - for (f1, f2) in fusiontrees(B) + for (f₁, f₂) in fusiontrees(B) local newtrees - for ((f1′, f2′), coeff′) in transpose(f1, f2, cindB, oindB) - for (f1′′, coeff′′) in artin_braid(f1′, 1, inv = inv_braid) + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + for (f₁′′, coeff′′) in artin_braid(f₁′, 1, inv = inv_braid) - f12 = (f1′′, f2′) + f12 = (f₁′′, f₂′) coeff = coeff′*coeff′′ if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff @@ -208,8 +208,8 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, end end end - for ((f1′,f2′), coeff) in newtrees - TO._add!(coeff*α, B[f1, f2], true, C[f1′,f2′], (reverse(cindB)...,oindB...)) + for ((f₁′,f₂′), coeff) in newtrees + TO._add!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], (reverse(cindB)...,oindB...)) end end return C @@ -239,11 +239,11 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, end braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindB[1]] > braidingtensor_levels[cindB[2]] - for (f1, f2) in fusiontrees(A) + for (f₁, f₂) in fusiontrees(A) local newtrees - for ((f1′,f2′), coeff′) in transpose(f1, f2, oindA, cindA) - for (f2′′, coeff′′) in artin_braid(f2′, 1, inv = inv_braid) - f12 = (f1′,f2′′) + for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + for (f₂′′, coeff′′) in artin_braid(f₂′, 1, inv = inv_braid) + f12 = (f₁′,f₂′′) coeff = coeff′*conj(coeff′′) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff @@ -252,8 +252,8 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, end end end - for ((f1′,f2′), coeff) in newtrees - TO._add!(coeff*α, A[f1, f2], true, C[f1′,f2′], (oindA...,reverse(cindA)...)) + for ((f₁′,f₂′), coeff) in newtrees + TO._add!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], (oindA...,reverse(cindA)...)) end end C @@ -290,27 +290,27 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, f₀ = FusionTree{I}((), u, (), (), ()) braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] - for (f1, f2) in fusiontrees(B) + for (f₁, f₂) in fusiontrees(B) local newtrees - for ((f1′,f2′), coeff′) in transpose(f1, f2, cindB, oindB) - f1′.coupled == u || continue - a = f1′.uncoupled[1] - b = f1′.uncoupled[2] - f1′.uncoupled[3] == dual(a) || continue - f1′.uncoupled[4] == dual(b) || continue + for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + f₁′.coupled == u || continue + a = f₁′.uncoupled[1] + b = f₁′.uncoupled[2] + f₁′.uncoupled[3] == dual(a) || continue + f₁′.uncoupled[4] == dual(b) || continue # should be automatic by matching spaces: - # f1′.isdual[1] != f1′.isdual[3] || continue - # f1′.isdual[2] != f1′.isdual[4] || continue - for (f1′′, coeff′′) in artin_braid(f1′, 2, inv = inv_braid) - f1′′.innerlines[1] == u || continue + # f₁′.isdual[1] != f₁′.isdual[3] || continue + # f₁′.isdual[2] != f₁′.isdual[4] || continue + for (f₁′′, coeff′′) in artin_braid(f₁′, 2, inv = inv_braid) + f₁′′.innerlines[1] == u || continue coeff = coeff′ * coeff′′ * sqrtdim(a) * sqrtdim(b) - if f1′′.isdual[1] + if f₁′′.isdual[1] coeff *= frobeniusschur(a) end - if f1′′.isdual[3] + if f₁′′.isdual[3] coeff *= frobeniusschur(b) end - f12 = (f₀, f2′) + f12 = (f₀, f₂′) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -319,8 +319,8 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, end end @isdefined(newtrees) || continue - for ((f1′, f2′), coeff) in newtrees - TO._trace!(coeff*α, B[f1, f2], true, C[f1′,f2′], oindB, + for ((f₁′, f₂′), coeff) in newtrees + TO._trace!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], oindB, (cindB[1], cindB[2]), (cindB[3], cindB[4])) end end @@ -358,27 +358,27 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, f₀ = FusionTree{I}((), u, (), (), ()) braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] - for (f1, f2) in fusiontrees(A) + for (f₁, f₂) in fusiontrees(A) local newtrees - for ((f1′, f2′), coeff′) in transpose(f1, f2, oindA, cindA) - f2′.coupled == u || continue - a = f2′.uncoupled[1] - b = f2′.uncoupled[2] - f2′.uncoupled[3] == dual(a) || continue - f2′.uncoupled[4] == dual(b) || continue + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + f₂′.coupled == u || continue + a = f₂′.uncoupled[1] + b = f₂′.uncoupled[2] + f₂′.uncoupled[3] == dual(a) || continue + f₂′.uncoupled[4] == dual(b) || continue # should be automatic by matching spaces: - # f2′.isdual[1] != f2′.isdual[3] || continue - # f2′.isdual[3] != f2′.isdual[4] || continue - for (f2′′, coeff′′) in artin_braid(f2′, 2, inv = inv_braid) - f2′′.innerlines[1] == u || continue + # f₂′.isdual[1] != f₂′.isdual[3] || continue + # f₂′.isdual[3] != f₂′.isdual[4] || continue + for (f₂′′, coeff′′) in artin_braid(f₂′, 2, inv = inv_braid) + f₂′′.innerlines[1] == u || continue coeff = coeff′ * conj(coeff′′ * sqrtdim(a) * sqrtdim(b)) - if f2′′.isdual[1] + if f₂′′.isdual[1] coeff *= conj(frobeniusschur(a)) end - if f2′′.isdual[3] + if f₂′′.isdual[3] coeff *= conj(frobeniusschur(b)) end - f12 = (f1′, f₀) + f12 = (f₁′, f₀) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -387,8 +387,8 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, end end @isdefined(newtrees) || continue - for ((f1′, f2′), coeff) in newtrees - TO._trace!(coeff*α, A[f1, f2], true, C[f1′,f2′], oindA, + for ((f₁′, f₂′), coeff) in newtrees + TO._trace!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], oindA, (cindA[1], cindA[2]), (cindA[3], cindA[4])) end end @@ -424,23 +424,23 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, u = one(I) braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] - for (f1, f2) in fusiontrees(B) + for (f₁, f₂) in fusiontrees(B) local newtrees - for ((f1′,f2′), coeff′) in transpose(f1, f2, cindB, oindB) - a = f1′.uncoupled[1] - b = f1′.uncoupled[2] - b == f1′.coupled || continue - a == dual(f1′.uncoupled[3]) || continue + for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + a = f₁′.uncoupled[1] + b = f₁′.uncoupled[2] + b == f₁′.coupled || continue + a == dual(f₁′.uncoupled[3]) || continue # should be automatic by matching spaces: - # f1′.isdual[1] != f1.isdual[3] || continue - for (f1′′, coeff′′) in artin_braid(f1′, 2, inv = inv_braid) - f1′′.innerlines[1] == u || continue + # f₁′.isdual[1] != f₁.isdual[3] || continue + for (f₁′′, coeff′′) in artin_braid(f₁′, 2, inv = inv_braid) + f₁′′.innerlines[1] == u || continue coeff = coeff′ * coeff′′ * sqrtdim(a) - if f1′′.isdual[1] + if f₁′′.isdual[1] coeff *= frobeniusschur(a) end - f1′′′ = FusionTree{I}((b,), b, (f1′′.isdual[3],), (), ()) - f12 = (f1′′′, f2′) + f₁′′′ = FusionTree{I}((b,), b, (f₁′′.isdual[3],), (), ()) + f12 = (f₁′′′, f₂′) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -449,8 +449,8 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, end end @isdefined(newtrees) || continue - for ((f1′,f2′), coeff) in newtrees - TO._trace!(coeff*α, B[f1, f2], true, C[f1′,f2′], + for ((f₁′,f₂′), coeff) in newtrees + TO._trace!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], (cindB[2], oindB...) , (cindB[1],), (cindB[3],)) end end @@ -486,23 +486,23 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, u = one(I) braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] - for (f1, f2) in fusiontrees(A) + for (f₁, f₂) in fusiontrees(A) local newtrees - for ((f1′,f2′), coeff′) in transpose(f1, f2, oindA, cindA) - a = f2′.uncoupled[1] - b = f2′.uncoupled[2] - b == f2′.coupled || continue - a == dual(f2′.uncoupled[3]) || continue + for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + a = f₂′.uncoupled[1] + b = f₂′.uncoupled[2] + b == f₂′.coupled || continue + a == dual(f₂′.uncoupled[3]) || continue # should be automatic by matching spaces: - # f2′.isdual[1] != f2.isdual[3] || continue - for (f2′′, coeff′′) in artin_braid(f2′, 2, inv = inv_braid) - f2′′.innerlines[1] == u || continue + # f₂′.isdual[1] != f₂.isdual[3] || continue + for (f₂′′, coeff′′) in artin_braid(f₂′, 2, inv = inv_braid) + f₂′′.innerlines[1] == u || continue coeff = coeff′ * conj(coeff′′ * sqrtdim(a)) - if f2′′.isdual[1] + if f₂′′.isdual[1] coeff *= conj(frobeniusschur(a)) end - f2′′′ = FusionTree{I}((b,), b, (f2′′.isdual[3],), (), ()) - f12 = (f1′, f2′′′) + f₂′′′ = FusionTree{I}((b,), b, (f₂′′.isdual[3],), (), ()) + f12 = (f₁′, f₂′′′) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -511,8 +511,8 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, end end @isdefined(newtrees) || continue - for ((f1′,f2′), coeff) in newtrees - TO._trace!(coeff*α, A[f1, f2], true, C[f1′,f2′], + for ((f₁′,f₂′), coeff) in newtrees + TO._trace!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], (oindA...,cindA[2]) , (cindA[1],), (cindA[3],)) end end diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 86e678ea..27cd0e7a 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -141,10 +141,10 @@ function twist!(t::AbstractTensorMap, i::Int; inv::Bool = false) end BraidingStyle(sectortype(t)) == Bosonic() && return t N₁ = numout(t) - for (f1, f2) in fusiontrees(t) - θ = i <= N₁ ? twist(f1.uncoupled[i]) : twist(f2.uncoupled[i-N₁]) + for (f₁, f₂) in fusiontrees(t) + θ = i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i-N₁]) inv && (θ = θ') - rmul!(t[f1, f2], θ) + rmul!(t[f₁, f₂], θ) end return t end diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 15b17ce7..d5500aaa 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -154,9 +154,9 @@ function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpa rowrc = get!(rowr, c) do FusionTreeDict{F₁, UnitRange{Int}}() end - for f1 in fusiontrees(s1, c, map(isdual, codom.spaces)) + for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) r = (offset1 + 1):(offset1 + dim(codom, s1)) - push!(rowrc, f1 => r) + push!(rowrc, f₁ => r) offset1 = last(r) end rowdims[c] = offset1 @@ -168,9 +168,9 @@ function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpa colrc = get!(colr, c) do FusionTreeDict{F₂, UnitRange{Int}}() end - for f2 in fusiontrees(s2, c, map(isdual, dom.spaces)) + for f₂ in fusiontrees(s2, c, map(isdual, dom.spaces)) r = (offset2 + 1):(offset2 + dim(dom, s2)) - push!(colrc, f2 => r) + push!(colrc, f₂ => r) offset2 = last(r) end coldims[c] = offset2 @@ -240,9 +240,9 @@ function TensorMap(f, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) wh rowrc = get!(rowr, c) do FusionTreeDict{F₁, UnitRange{Int}}() end - for f1 in fusiontrees(s1, c, map(isdual, codom.spaces)) + for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) r = (offset1 + 1):(offset1 + dim(codom, s1)) - push!(rowrc, f1 => r) + push!(rowrc, f₁ => r) offset1 = last(r) end rowdims[c] = offset1 @@ -254,9 +254,9 @@ function TensorMap(f, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) wh colrc = get!(colr, c) do FusionTreeDict{F₂, UnitRange{Int}}() end - for f2 in fusiontrees(s2, c, map(isdual, dom.spaces)) + for f₂ in fusiontrees(s2, c, map(isdual, dom.spaces)) r = (offset2 + 1):(offset2 + dim(dom, s2)) - push!(colrc, f2 => r) + push!(colrc, f₂ => r) offset2 = last(r) end coldims[c] = offset2 @@ -435,34 +435,34 @@ fusiontrees(t::TensorMap) = TensorKeyIterator(t.rowr, t.colr) c2 == c1 || throw(SectorMismatch("Not a valid sector for this tensor")) hassector(codomain(t), s1) && hassector(domain(t), s2) end - f1 = FusionTree(s1, c1, map(isdual, tuple(codomain(t)...))) - f2 = FusionTree(s2, c1, map(isdual, tuple(domain(t)...))) + f₁ = FusionTree(s1, c1, map(isdual, tuple(codomain(t)...))) + f₂ = FusionTree(s2, c1, map(isdual, tuple(domain(t)...))) @inbounds begin - return t[f1,f2] + return t[f₁,f₂] end end @propagate_inbounds Base.getindex(t::TensorMap, sectors::Tuple) = t[map(sectortype(t), sectors)] @inline function Base.getindex(t::TensorMap{<:IndexSpace,N₁,N₂,I}, - f1::FusionTree{I,N₁}, f2::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} + f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} - c = f1.coupled + c = f₁.coupled @boundscheck begin - c == f2.coupled || throw(SectorMismatch()) - haskey(t.rowr[c], f1) || throw(SectorMismatch()) - haskey(t.colr[c], f2) || throw(SectorMismatch()) + c == f₂.coupled || throw(SectorMismatch()) + haskey(t.rowr[c], f₁) || throw(SectorMismatch()) + haskey(t.colr[c], f₂) || throw(SectorMismatch()) end @inbounds begin - d = (dims(codomain(t), f1.uncoupled)..., dims(domain(t), f2.uncoupled)...) - return sreshape(StridedView(t.data[c])[t.rowr[c][f1], t.colr[c][f2]], d) + d = (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...) + return sreshape(StridedView(t.data[c])[t.rowr[c][f₁], t.colr[c][f₂]], d) end end @propagate_inbounds Base.setindex!(t::TensorMap{<:IndexSpace,N₁,N₂,I}, v, - f1::FusionTree{I,N₁}, - f2::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} = - copy!(getindex(t, f1, f2), v) + f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} = + copy!(getindex(t, f₁, f₂), v) # For a tensor with trivial symmetry, allow no argument indexing @inline Base.getindex(t::TrivialTensorMap) = @@ -502,15 +502,15 @@ function Base.show(io::IO, t::TensorMap{S}) where {S<:IndexSpace} Base.print_array(io, t[]) println(io) elseif FusionStyle(sectortype(S)) isa UniqueFusion - for (f1,f2) in fusiontrees(t) - println(io, "* Data for sector ", f1.uncoupled, " ← ", f2.uncoupled, ":") - Base.print_array(io, t[f1,f2]) + for (f₁,f₂) in fusiontrees(t) + println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") + Base.print_array(io, t[f₁,f₂]) println(io) end else - for (f1,f2) in fusiontrees(t) - println(io, "* Data for fusiontree ", f1, " ← ", f2, ":") - Base.print_array(io, t[f1,f2]) + for (f₁,f₂) in fusiontrees(t) + println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") + Base.print_array(io, t[f₁,f₂]) println(io) end end @@ -550,8 +550,8 @@ Base.convert(::Type{TensorMap}, t::TensorMap) = t Base.convert(::Type{TensorMap}, t::AbstractTensorMap) = copy!(TensorMap(undef, scalartype(t), codomain(t), domain(t)), t) -function Base.convert(T::Type{TensorMap{S,N₁,N₂,I,A,F1,F2}}, - t::AbstractTensorMap{S,N₁,N₂}) where {S,N₁,N₂,I,A,F1,F2} +function Base.convert(T::Type{TensorMap{S,N₁,N₂,I,A,F₁,F₂}}, + t::AbstractTensorMap{S,N₁,N₂}) where {S,N₁,N₂,I,A,F₁,F₂} if typeof(t) == T return t else diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 2a1c63ec..22cfe281 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -47,7 +47,7 @@ end β, tdst::AbstractTensorMap{S,N₁,N₂}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {S,N₁,N₂} - return _add!(α, tsrc, β, tdst, p1, p2, (f1, f2) -> permute(f1, f2, p1, p2)) + return _add!(α, tsrc, β, tdst, p1, p2, (f₁, f₂) -> permute(f₁, f₂, p1, p2)) end @propagate_inbounds function add_braid!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, @@ -60,13 +60,13 @@ end levels1 = TupleTools.getindices(levels, codomainind(tsrc)) levels2 = TupleTools.getindices(levels, domainind(tsrc)) return _add!(α, tsrc, β, tdst, p1, p2, - (f1, f2) -> braid(f1, f2, levels1, levels2, p1, p2)) + (f₁, f₂) -> braid(f₁, f₂, levels1, levels2, p1, p2)) end @propagate_inbounds function add_transpose!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {S,N₁,N₂} - return _add!(α, tsrc, β, tdst, p1, p2, (f1, f2) -> transpose(f1, f2, p1, p2)) + return _add!(α, tsrc, β, tdst, p1, p2, (f₁, f₂) -> transpose(f₁, f₂, p1, p2)) end function _add!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, @@ -108,14 +108,14 @@ function _add_abelian_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTen if Threads.nthreads() > 1 nstridedthreads = Strided.get_num_threads() Strided.set_num_threads(1) - Threads.@sync for (f1, f2) in fusiontrees(tsrc) - Threads.@spawn _addabelianblock!(α, tsrc, β, tdst, p1, p2, f1, f2, + Threads.@sync for (f₁, f₂) in fusiontrees(tsrc) + Threads.@spawn _addabelianblock!(α, tsrc, β, tdst, p1, p2, f₁, f₂, fusiontreemap) end Strided.set_num_threads(nstridedthreads) else # debugging is easier this way - for (f1, f2) in fusiontrees(tsrc) - _addabelianblock!(α, tsrc, β, tdst, p1, p2, f1, f2, fusiontreemap) + for (f₁, f₂) in fusiontrees(tsrc) + _addabelianblock!(α, tsrc, β, tdst, p1, p2, f₁, f₂, fusiontreemap) end end return nothing @@ -124,13 +124,13 @@ end function _addabelianblock!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple, - f1::FusionTree, f2::FusionTree, + f₁::FusionTree, f₂::FusionTree, fusiontreemap) cod = codomain(tsrc) dom = domain(tsrc) - (f1′, f2′), coeff = first(fusiontreemap(f1, f2)) + (f₁′, f₂′), coeff = first(fusiontreemap(f₁, f₂)) pdata = (p1..., p2...) - @inbounds axpby!(α * coeff, permutedims(tsrc[f1, f2], pdata), β, tdst[f1′, f2′]) + @inbounds axpby!(α * coeff, permutedims(tsrc[f₁, f₂], pdata), β, tdst[f₁′, f₂′]) end function _add_general_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, @@ -144,9 +144,9 @@ function _add_general_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTen elseif β != 1 mul!(tdst, β, tdst) end - for (f1, f2) in fusiontrees(tsrc) - for ((f1′, f2′), coeff) in fusiontreemap(f1, f2) - @inbounds axpy!(α * coeff, permutedims(tsrc[f1, f2], pdata), tdst[f1′, f2′]) + for (f₁, f₂) in fusiontrees(tsrc) + for ((f₁′, f₂′), coeff) in fusiontreemap(f₁, f₂) + @inbounds axpy!(α * coeff, permutedims(tsrc[f₁, f₂], pdata), tdst[f₁′, f₂′]) end end return nothing @@ -191,10 +191,10 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N end r1 = (p1..., q1...) r2 = (p2..., q2...) - for (f1, f2) in fusiontrees(tsrc) - for ((f1′, f2′), coeff) in permute(f1, f2, r1, r2) - f1′′, g1 = split(f1′, N₁) - f2′′, g2 = split(f2′, N₂) + for (f₁, f₂) in fusiontrees(tsrc) + for ((f₁′, f₂′), coeff) in permute(f₁, f₂, r1, r2) + f₁′′, g1 = split(f₁′, N₁) + f₂′′, g2 = split(f₂′, N₂) if g1 == g2 coeff *= dim(g1.coupled) / dim(g1.uncoupled[1]) for i in 2:length(g1.uncoupled) @@ -202,7 +202,7 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N coeff *= twist(g1.uncoupled[i]) end end - TO.tensortrace!(tdst[f1′′, f2′′], (p1, p2), tsrc[f1, f2], (q1, q2), :N, α*coeff, true) + TO.tensortrace!(tdst[f₁′′, f₂′′], (p1, p2), tsrc[f₁, f₂], (q1, q2), :N, α*coeff, true) end end end diff --git a/src/tensors/tensortreeiterator.jl b/src/tensors/tensortreeiterator.jl index d5c78149..55783e40 100644 --- a/src/tensors/tensortreeiterator.jl +++ b/src/tensors/tensortreeiterator.jl @@ -42,26 +42,26 @@ function Base.iterate(it::TensorKeyIterator) rownext = iterate(rowit) colnext = iterate(colit) end - (f1, r1), rowstate = rownext - (f2, r2), colstate = colnext + (f₁, r1), rowstate = rownext + (f₂, r2), colstate = colnext - return (f1, f2), (f2, i, rowstate, colstate) + return (f₁, f₂), (f₂, i, rowstate, colstate) end function Base.iterate(it::TensorKeyIterator, state) - (f2, i, rowstate, colstate) = state + (f₂, i, rowstate, colstate) = state rowit, colit = it.rowr.values[i], it.colr.values[i] rownext = iterate(rowit, rowstate) if rownext !== nothing - (f1, r1), rowstate = rownext - return (f1, f2), (f2, i, rowstate, colstate) + (f₁, r1), rowstate = rownext + return (f₁, f₂), (f₂, i, rowstate, colstate) end colnext = iterate(colit, colstate) if colnext !== nothing rownext = iterate(rowit) # should not be nothing @assert rownext !== nothing - (f1, r1), rowstate = rownext - (f2, r2), colstate = colnext - return (f1, f2), (f2, i, rowstate, colstate) + (f₁, r1), rowstate = rownext + (f₂, r2), colstate = colnext + return (f₁, f₂), (f₂, i, rowstate, colstate) end while true if rownext === nothing @@ -76,8 +76,8 @@ function Base.iterate(it::TensorKeyIterator, state) rownext = iterate(rowit) colnext = iterate(colit) end - (f1, r1), rowstate = rownext - (f2, r2), colstate = colnext + (f₁, r1), rowstate = rownext + (f₂, r2), colstate = colnext - return (f1, f2), (f2, i, rowstate, colstate) + return (f₁, f₂), (f₂, i, rowstate, colstate) end From 3a434690f91f442b901c0218041a0b638d2d64d4 Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 12:48:25 +0200 Subject: [PATCH 18/57] complete eltype to scalartype transition --- src/TensorKit.jl | 1 + src/auxiliary/deprecate.jl | 4 ++++ src/tensors/abstracttensor.jl | 31 +++++++++++++-------------- src/tensors/braidingtensor.jl | 18 ++++++++-------- src/tensors/factorizations.jl | 38 ++++++++++++++++----------------- src/tensors/linalg.jl | 16 +++++++------- src/tensors/tensor.jl | 15 +++++++------ src/tensors/tensoroperations.jl | 6 ++---- src/tensors/vectorinterface.jl | 1 + test/tensors.jl | 22 ++++++++++--------- 10 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 src/tensors/vectorinterface.jl diff --git a/src/TensorKit.jl b/src/TensorKit.jl index bbadf3f7..2ccb3e49 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -181,6 +181,7 @@ include("tensors/tensortreeiterator.jl") include("tensors/tensor.jl") include("tensors/adjoint.jl") include("tensors/linalg.jl") +include("tensors/vectorinterface.jl") include("tensors/tensoroperations.jl") include("tensors/indexmanipulations.jl") include("tensors/truncation.jl") diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index c688bbcf..8a90cf57 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -57,3 +57,7 @@ function Base.getindex(::ComplexNumbers, d::Int) @warn "`ℂ[d]` is deprecated, use `ℂ^d` or `ComplexSpace(d)`." maxlog = 1 return ℂ^d end + +import Base: eltype +@deprecate eltype(T::Type{<:AbstractTensorMap}) scalartype(T) +@deprecate eltype(t::AbstractTensorMap) scalartype(t) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index d310ac40..7f24a60a 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -23,14 +23,20 @@ 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(T::Type{<:AbstractTensorMap}) = eltype(storagetype(T)) +spacetype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = S +sectortype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = sectortype(S) +function InnerProductStyle(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} + return InnerProductStyle(S) +end +field(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = field(S) +numout(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ +numin(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₂ +numind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ + N₂ + function similarstoragetype(TT::Type{<:AbstractTensorMap}, ::Type{T}) where {T} return Core.Compiler.return_type(similar, Tuple{storagetype(TT),Type{T}}) end -storagetype(t::AbstractTensorMap) = storagetype(typeof(t)) -similarstoragetype(t::AbstractTensorMap, T) = similarstoragetype(typeof(t), T) -Base.eltype(t::AbstractTensorMap) = eltype(typeof(t)) spacetype(t::AbstractTensorMap) = spacetype(typeof(t)) sectortype(t::AbstractTensorMap) = sectortype(typeof(t)) InnerProductStyle(t::AbstractTensorMap) = InnerProductStyle(typeof(t)) @@ -39,15 +45,8 @@ numout(t::AbstractTensorMap) = numout(typeof(t)) numin(t::AbstractTensorMap) = numin(typeof(t)) numind(t::AbstractTensorMap) = numind(typeof(t)) -spacetype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = S -sectortype(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = sectortype(S) -function InnerProductStyle(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} - return InnerProductStyle(S) -end -field(::Type{<:AbstractTensorMap{S}}) where {S<:IndexSpace} = field(S) -numout(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ -numin(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₂ -numind(::Type{<:AbstractTensorMap{<:IndexSpace,N₁,N₂}}) where {N₁,N₂} = N₁ + N₂ +storagetype(t::AbstractTensorMap) = storagetype(typeof(t)) +similarstoragetype(t::AbstractTensorMap, T) = similarstoragetype(typeof(t), T) const order = numind @@ -138,11 +137,11 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap{S,N₁,N₂}) where {S reshape(F₂, TupleTools.prod(d2), sz2[end])', (d1..., d2...)) if !(@isdefined A) if eltype(F) <: Complex - T = complex(float(eltype(t))) + T = complex(float(scalartype(t))) elseif eltype(F) <: Integer - T = eltype(t) + T = scalartype(t) else - T = float(eltype(t)) + T = float(scalartype(t)) end A = fill(zero(T), (dims(cod)..., dims(dom)...)) end diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index a443edd7..1e34ba9c 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -91,7 +91,7 @@ end d = (dims(V2 ⊗ V1, f₁.uncoupled)..., dims(V1 ⊗ V2, f₂.uncoupled)...) n1 = d[1]*d[2] n2 = d[3]*d[4] - data = fill!(storagetype(b)(undef, (n1, n2)), zero(eltype(b))) + data = fill!(storagetype(b)(undef, (n1, n2)), zero(scalartype(b))) a1, a2 = f₂.uncoupled if f₁.uncoupled == (a2, a1) braiddict = artin_braid(f₂, 1; inv = b.adjoint) @@ -109,17 +109,17 @@ end Base.copy(b::BraidingTensor) = copy!(similar(b), b) function Base.copy!(t::TensorMap, b::BraidingTensor) space(t) == space(b) || throw(SectorMismatch()) - fill!(t, zero(eltype(t))) + fill!(t, zero(scalartype(t))) for (f₁, f₂) in fusiontrees(t) data = t[f₁, f₂] if sectortype(t) == Trivial - r = one(eltype(t)) + r = one(scalartype(t)) else a1, a2 = f₂.uncoupled c = f₂.coupled f₁.uncoupled == (a2, a1) || continue braiddict = artin_braid(f₂, 1; inv = b.adjoint) - r = convert(eltype(t), get(braiddict, f₁, zero(valtype(braiddict)))) + r = convert(scalartype(t), get(braiddict, f₁, zero(valtype(braiddict)))) end for i = 1:size(data, 1), j = 1:size(data, 2) data[i, j, j, i] = r @@ -136,16 +136,16 @@ function block(b::BraidingTensor, s::Sector) if sectortype(b) == Trivial d1, d2 = dim(V1), dim(V2) n = d1*d2 - data = fill!(storagetype(b)(undef, (n, n)), zero(eltype(b))) + data = fill!(storagetype(b)(undef, (n, n)), zero(scalartype(b))) si = 1 + d2*d1*d1 sj = d2 + d2*d1 @inbounds for i = 1:d2, j = 1:d1 - data[(i-1)*si + (j-1)*sj + 1] = one(eltype(b)) + data[(i-1)*si + (j-1)*sj + 1] = one(scalartype(b)) end return data end n = blockdim(domain(b), s) - data = fill!(storagetype(b)(undef, (n, n)), zero(eltype(b))) + data = fill!(storagetype(b)(undef, (n, n)), zero(scalartype(b))) iter = fusiontrees(b) # actually contains information about ranges as well for (f₂, r2) in iter.colr[s] for (f₁, r1) in iter.rowr[s] @@ -154,7 +154,7 @@ function block(b::BraidingTensor, s::Sector) d2 = dim(V2, a2) f₁.uncoupled == (a2, a1) || continue braiddict = artin_braid(f₂, 1; inv = b.adjoint) - r = convert(eltype(b), get(braiddict, f₁, zero(valtype(braiddict)))) + r = convert(scalartype(b), get(braiddict, f₁, zero(valtype(braiddict)))) si = 1 + n*d1 sj = d2 + n start = first(r1) + (first(r2)-1) * n @@ -521,6 +521,6 @@ end has_shared_permute(t::BraidingTensor, args...) = false function cached_permute(sym::Symbol, t::BraidingTensor, p1, p2; copy=false) - tp = TO.cached_similar_from_indices(sym, eltype(t), p1, p2, t, :N) + tp = TO.cached_similar_from_indices(sym, scalartype(t), p1, p2, t, :N) return add!(true, t, false, tp, p1, p2) end diff --git a/src/tensors/factorizations.jl b/src/tensors/factorizations.jl index 6c0bc65a..29c7970d 100644 --- a/src/tensors/factorizations.jl +++ b/src/tensors/factorizations.jl @@ -134,7 +134,7 @@ leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = rightnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; alg::OrthogonalFactorizationAlgorithm = LQ(), atol::Real = 0.0, - rtol::Real = eps(real(float(one(eltype(t)))))*iszero(atol)) -> N + 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`. @@ -208,7 +208,7 @@ eig(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = 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 `eltype` as `t`. See `eig` and `eigen` for non-hermitian tensors. Hermiticity +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) === EuclideanProduct()`. @@ -302,9 +302,9 @@ end function leftorth!(t::TensorMap; alg::Union{QR,QRpos,QL,QLpos,SVD,SDD,Polar}=QRpos(), - atol::Real=zero(float(real(eltype(t)))), - rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(eltype(t)))) : - eps(real(float(one(eltype(t))))) * iszero(atol)) + atol::Real=zero(float(real(scalartype(t)))), + rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(scalartype(t)))) : + eps(real(float(one(scalartype(t))))) * iszero(atol)) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("leftorth! only defined for Euclidean inner product spaces")) if !iszero(rtol) @@ -339,9 +339,9 @@ end function leftnull!(t::TensorMap; alg::Union{QR,QRpos,SVD,SDD}=QRpos(), - atol::Real=zero(float(real(eltype(t)))), - rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(eltype(t)))) : - eps(real(float(one(eltype(t))))) * iszero(atol)) + atol::Real=zero(float(real(scalartype(t)))), + rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(scalartype(t)))) : + eps(real(float(one(scalartype(t))))) * iszero(atol)) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("leftnull! only defined for Euclidean inner product spaces")) if !iszero(rtol) @@ -364,9 +364,9 @@ end function rightorth!(t::TensorMap; alg::Union{LQ,LQpos,RQ,RQpos,SVD,SDD,Polar}=LQpos(), - atol::Real=zero(float(real(eltype(t)))), - rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(eltype(t)))) : - eps(real(float(one(eltype(t))))) * iszero(atol)) + atol::Real=zero(float(real(scalartype(t)))), + rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(scalartype(t)))) : + eps(real(float(one(scalartype(t))))) * iszero(atol)) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("rightorth! only defined for Euclidean inner product spaces")) if !iszero(rtol) @@ -401,9 +401,9 @@ end function rightnull!(t::TensorMap; alg::Union{LQ,LQpos,SVD,SDD}=LQpos(), - atol::Real=zero(float(real(eltype(t)))), - rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(eltype(t)))) : - eps(real(float(one(eltype(t))))) * iszero(atol)) + atol::Real=zero(float(real(scalartype(t)))), + rtol::Real=(alg ∉ (SVD(), SDD())) ? zero(float(real(scalartype(t)))) : + eps(real(float(one(scalartype(t))))) * iszero(atol)) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("rightnull! only defined for Euclidean inner product spaces")) if !iszero(rtol) @@ -433,14 +433,14 @@ function tsvd!(t::TensorMap; S = spacetype(t) I = sectortype(t) A = storagetype(t) - Ar = similarstoragetype(t, real(eltype(t))) + Ar = similarstoragetype(t, real(scalartype(t))) Udata = SectorDict{I, A}() Σmdata = SectorDict{I, Ar}() # this will contain the singular values as matrix Vdata = SectorDict{I, A}() dims = SectorDict{sectortype(t), Int}() if isempty(blocksectors(t)) W = S(dims) - truncerr = zero(real(eltype(t))) + truncerr = zero(real(scalartype(t))) return TensorMap(Udata, codomain(t)←W), TensorMap(Σmdata, W←W), TensorMap(Vdata, W←domain(t)), truncerr end @@ -481,7 +481,7 @@ function tsvd!(t::TensorMap; elseif length(codomain(t)) == 1 && codomain(t)[1] ≅ W W = codomain(t)[1] end - truncerr = abs(zero(eltype(t))) + truncerr = abs(zero(scalartype(t))) end for (c, Σ) in Σdata Σmdata[c] = copyto!(similar(Σ, length(Σ), length(Σ)), Diagonal(Σ)) @@ -509,7 +509,7 @@ function eigh!(t::TensorMap; kwargs...) S = spacetype(t) I = sectortype(t) A = storagetype(t) - Ar = similarstoragetype(t, real(eltype(t))) + Ar = similarstoragetype(t, real(scalartype(t))) Ddata = SectorDict{I, Ar}() Vdata = SectorDict{I, A}() dims = SectorDict{I, Int}() @@ -533,7 +533,7 @@ function eig!(t::TensorMap; kwargs...) throw(SpaceMismatch("`eig!` requires domain and codomain to be the same")) S = spacetype(t) I = sectortype(t) - T = complex(eltype(t)) + T = complex(scalartype(t)) Ac = similarstoragetype(t, T) Ddata = SectorDict{I, Ac}() Vdata = SectorDict{I, Ac}() diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 0e85da5c..7472a72b 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -25,7 +25,7 @@ LinearAlgebra.normalize(t::AbstractTensorMap, p::Real = 2) = mul!(similar(t), t, inv(norm(t, p))) Base.:*(t1::AbstractTensorMap, t2::AbstractTensorMap) = - mul!(similar(t1, promote_type(eltype(t1), eltype(t2)), codomain(t1)←domain(t2)), t1, t2) + mul!(similar(t1, promote_type(scalartype(t1), scalartype(t2)), codomain(t1)←domain(t2)), t1, t2) Base.exp(t::AbstractTensorMap) = exp!(copy(t)) Base.:^(t::AbstractTensorMap, p::Integer) = p < 0 ? Base.power_by_squaring(inv(t), -p) : Base.power_by_squaring(t, p) @@ -367,17 +367,17 @@ for f in (:cos, :sin, :tan, :cot, :cosh, :sinh, :tanh, :coth, :atan, :acot, :asi domain(t) == codomain(t) || error("$sf of a tensor only exist when domain == codomain.") I = sectortype(t) - T = similarstoragetype(t, float(eltype(t))) + T = similarstoragetype(t, float(scalartype(t))) if sectortype(t) === Trivial local data::T - if eltype(t) <: Real + if scalartype(t) <: Real data = real($f(block(t, Trivial()))) else data = $f(block(t, Trivial())) end return TensorMap(data, codomain(t), domain(t)) else - if eltype(t) <: Real + if scalartype(t) <: Real datadict = SectorDict{I, T}(c=>real($f(b)) for (c, b) in blocks(t)) else datadict = SectorDict{I, T}(c=>$f(b) for (c, b) in blocks(t)) @@ -393,7 +393,7 @@ for f in (:sqrt, :log, :asin, :acos, :acosh, :atanh, :acoth) domain(t) == codomain(t) || error("$sf of a tensor only exist when domain == codomain.") I = sectortype(t) - T = similarstoragetype(t, complex(float(eltype(t)))) + T = similarstoragetype(t, complex(float(scalartype(t)))) if sectortype(t) === Trivial data::T = $f(block(t, Trivial())) return TensorMap(data, codomain(t), domain(t)) @@ -414,7 +414,7 @@ function catdomain(t1::AbstractTensorMap{S, N₁, 1}, t2::AbstractTensorMap{S, N throw(SpaceMismatch("cannot horizontally concatenate tensors whose domain has non-matching duality")) V = V1 ⊕ V2 - t = TensorMap(undef, promote_type(eltype(t1), eltype(t2)), codomain(t1), V) + t = TensorMap(undef, promote_type(scalartype(t1), scalartype(t2)), codomain(t1), V) for c in sectors(V) block(t, c)[:, 1:dim(V1, c)] .= block(t1, c) block(t, c)[:, dim(V1, c) .+ (1:dim(V2, c))] .= block(t2, c) @@ -430,7 +430,7 @@ function catcodomain(t1::AbstractTensorMap{S, 1, N₂}, t2::AbstractTensorMap{S, throw(SpaceMismatch("cannot vertically concatenate tensors whose codomain has non-matching duality")) V = V1 ⊕ V2 - t = TensorMap(undef, promote_type(eltype(t1), eltype(t2)), V, domain(t1)) + t = TensorMap(undef, promote_type(scalartype(t1), scalartype(t2)), V, domain(t1)) for c in sectors(V) block(t, c)[1:dim(V1, c), :] .= block(t1, c) block(t, c)[dim(V1, c) .+ (1:dim(V2, c)), :] .= block(t2, c) @@ -451,7 +451,7 @@ function ⊗(t1::AbstractTensorMap{S}, t2::AbstractTensorMap{S}) where S dom1, dom2 = domain(t1), domain(t2) cod = cod1 ⊗ cod2 dom = dom1 ⊗ dom2 - t = TensorMap(zeros, promote_type(eltype(t1), eltype(t2)), cod, dom) + t = TensorMap(zeros, promote_type(scalartype(t1), scalartype(t2)), cod, dom) if sectortype(S) === Trivial d1 = dim(cod1) d2 = dim(cod2) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index d5500aaa..4bf24646 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -19,15 +19,16 @@ struct TensorMap{S<:IndexSpace, N₁, N₂, I<:Sector, A<:Union{<:DenseMatrix,Se colr::SectorDict{I,FusionTreeDict{F₂,UnitRange{Int}}}) where {S<:IndexSpace, N₁, N₂, I<:Sector, A<:SectorDict{I,<:DenseMatrix}, F₁<:FusionTree{I,N₁}, F₂<:FusionTree{I,N₂}} - eltype(valtype(data)) ⊆ field(S) || - @warn("eltype(data) = $(eltype(data)) ⊈ $(field(S)))", maxlog=1) + T = scalartype(valtype(data)) + T ⊆ field(S) || @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog=1) new{S, N₁, N₂, I, A, F₁, F₂}(data, codom, dom, rowr, colr) end function TensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data::A, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) where {S<:IndexSpace, N₁, N₂, A<:DenseMatrix} - eltype(data) ⊆ field(S) || - @warn("eltype(data) = $(eltype(data)) ⊈ $(field(S)))", maxlog=1) + T = scalartype(data) + T ⊆ field(S) || + @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog=1) new{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data, codom, dom) end end @@ -109,8 +110,8 @@ function TensorMap(data::DenseArray, codom::ProductSpace{S,N₁}, dom::ProductSp if norm(basis*lhs - rhs) > tol throw(ArgumentError("Data has non-zero elements at incompatible positions")) end - if eltype(lhs) != eltype(t) - t2 = TensorMap(zeros, promote_type(eltype(lhs), eltype(t)), codom, dom) + if eltype(lhs) != scalartype(t) + t2 = TensorMap(zeros, promote_type(eltype(lhs), scalartype(t)), codom, dom) else t2 = t end @@ -362,7 +363,7 @@ Base.similar(t::AbstractTensorMap{S}, P::TensorSpace{S}) where {S} = Tensor(d->storagetype(t)(undef, d), P) function Base.complex(t::AbstractTensorMap) - if eltype(t) <: Complex + if scalartype(t) <: Complex return t elseif t.data isa AbstractArray return TensorMap(complex(t.data), codomain(t), domain(t)) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 22cfe281..4cbf1971 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -13,7 +13,7 @@ function cached_permute(sym::Symbol, t::TensorMap{S}, end # general case @inbounds begin - tp = TO.cached_similar_from_indices(sym, eltype(t), p1, p2, t, :N) + tp = TO.cached_similar_from_indices(sym, scalartype(t), p1, p2, t, :N) return add!(true, t, false, tp, p1, p2) end end @@ -309,7 +309,7 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, else p1′ = ntuple(identity, N₁) p2′ = N₁ .+ ntuple(identity, N₂) - TC = eltype(C) + TC = scalartype(C) C′ = TO.cached_similar_from_indices(syms[3], TC, oindA, oindB, p1′, p2′, A, B, :N, :N) mul!(C′, A′, B′) @@ -506,5 +506,3 @@ end function TO.tensoralloc(ttype::Type{<:AbstractTensorMap}, structure, istemp=false) return TensorMap(undef, scalartype(ttype), structure) end - -VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = eltype(T) \ No newline at end of file diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl new file mode 100644 index 00000000..ecc17c16 --- /dev/null +++ b/src/tensors/vectorinterface.jl @@ -0,0 +1 @@ +VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = scalartype(storagetype(T)) diff --git a/test/tensors.jl b/test/tensors.jl index 3d59c654..a7505bfe 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -85,7 +85,7 @@ for V in spacelist for T in (Int, Float32, Float64, ComplexF32, ComplexF64, BigFloat) t = Tensor(zeros, T, W) @test @constinferred(hash(t)) == hash(deepcopy(t)) - @test eltype(t) == T + @test scalartype(t) == T @test norm(t) == 0 @test codomain(t) == W @test space(t) == (W ← one(W)) @@ -119,7 +119,7 @@ for V in spacelist W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Float32, ComplexF64) t = TensorMap(rand, T, W) - @test eltype(t) == T + @test scalartype(t) == T @test space(t) == W @test space(t') == W' @test dim(t) == dim(space(t)) @@ -337,10 +337,11 @@ for V in spacelist # Test both a normal tensor and an adjoint one. ts = (Tensor(rand, T, W), Tensor(rand, T, W)') for t in ts - @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + @testset "leftorth with $alg" for alg in + (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) QdQ = Q' * Q @test QdQ ≈ one(QdQ) @@ -389,10 +390,11 @@ for V in spacelist end @testset "empty tensor" begin t = TensorMap(randn, T, V1 ⊗ V2, typeof(V1)()) - @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + @testset "leftorth with $alg" for alg in + (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) Q, R = @constinferred leftorth(t; alg=alg) @test Q == t @test dim(Q) == dim(R) == 0 From a5df79ecc200812c29b2fe12e8a024cf9a1fcf9a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 25 Jul 2023 10:18:36 -0400 Subject: [PATCH 19/57] Update logo make clips transparent for non-white backgrounds --- docs/src/assets/logo.svg | 123 +++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 43 deletions(-) diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index 75413d4c..842c3e24 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -1,19 +1,19 @@ + sodipodi:docname="logo.svg" + inkscape:version="1.2.2 (1:1.2.2+202305151915+b0a8486541)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -27,7 +27,53 @@ + id="defs13"> + + + + + + + + + + + + inkscape:guide-bbox="true" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="pt"> + id="path973" + clip-path="url(#clipPath1894)" + inkscape:path-effect="#path-effect1898" + inkscape:original-d="m 187.44606,89.123138 c -18.55695,0 -30.85198,-45.785824 -30.8575,-56.705107" /> - - - + clip-path="url(#clipPath1158)" + inkscape:path-effect="#path-effect1162" + inkscape:original-d="m 262.39624,289.58078 c 0.50861,-27.65502 -97.35694,-30.66613 -140.53816,-54.65202 -12.27712,-6.81958 -20.13379,-15.33466 -20.0523,-26.5937 0.0746,-10.30152 0.002,-12.31738 0.55823,-45.43791" /> + inkscape:connector-curvature="0" + sodipodi:insensitive="true" /> Date: Tue, 25 Jul 2023 10:30:00 -0400 Subject: [PATCH 20/57] Add dark mode logo --- README.md | 5 +- docs/src/assets/logo-dark.svg | 216 ++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 docs/src/assets/logo-dark.svg diff --git a/README.md b/README.md index 7d28e367..26ce9c02 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ - + + + TensorKit.jl logo + # TensorKit.jl diff --git a/docs/src/assets/logo-dark.svg b/docs/src/assets/logo-dark.svg new file mode 100644 index 00000000..8632e2bd --- /dev/null +++ b/docs/src/assets/logo-dark.svg @@ -0,0 +1,216 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8c7767d0c979a1b85107af1ac1f39643f7940fd7 Mon Sep 17 00:00:00 2001 From: Jutho Date: Tue, 25 Jul 2023 17:17:06 +0200 Subject: [PATCH 21/57] improve blocksectors for spaces --- src/spaces/homspace.jl | 25 ++++++++++++++++++++++++- src/spaces/productspace.jl | 23 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 6d390e65..67083231 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -67,13 +67,36 @@ end 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`](@ref). """ function blocksectors(W::HomSpace) sectortype(W) === Trivial && return TrivialOrEmptyIterator(dim(domain(W)) == 0 || dim(codomain(W)) == 0) - return intersect(blocksectors(codomain(W)), blocksectors(domain(W))) + + codom = codomain(W) + dom = domain(W) + N₁ = length(codom) + N₂ = length(dom) + I = sectortype(W) + if N₁ == 0 || N₂ == 0 + return (one(I),) + elseif N₂ <= N₁ + return filter!(c->hasblock(codom, c), collect(blocksectors(dom))) + else + return filter!(c->hasblock(dom, c), collect(blocksectors(codom))) + end end +""" + hasblock(W::HomSpace, c::Sector) + +Query whether a coupled sector `c` appears in both the codomain and domain of `W`. + +See also [`blocksectors`](@ref). +""" +hasblock(W::HomSpace, c::Sector) = hasblock(codomain(W), c) && hasblock(domain(W), c) + """ dim(W::HomSpace) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index a2c338f9..e4630765 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -63,7 +63,6 @@ _sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{Trivial}) where {N} = _sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{<:Sector}) where {N} = product(map(sectors, P.spaces)...) - """ hassector(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace} -> Bool @@ -128,12 +127,32 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} return bs end +""" + 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`](@ref) and [`blocksectors`](@ref). +""" +function hasblock(P::ProductSpace, c::Sector) + sectortype(P) == typeof(c) || throw(SectorMismatch()) + for s in sectors(P) + if !isempty(fusiontrees(s, c)) + return true + end + end + return false +end + """ 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`](@ref) and [`blocksectors`](@ref). """ function blockdim(P::ProductSpace, c::Sector) sectortype(P) == typeof(c) || throw(SectorMismatch()) @@ -190,7 +209,7 @@ fuse(P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) 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 a the `S`-equivalent of the +`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. From 59ac5089c95fd43b14d7931ad271a6b1fddbdfd5 Mon Sep 17 00:00:00 2001 From: Jutho Date: Wed, 26 Jul 2023 10:19:58 +0200 Subject: [PATCH 22/57] replace TrivialOrNothingIterator with more general OneOrNoneIterator --- src/TensorKit.jl | 1 + src/auxiliary/iterators.jl | 19 +++++++++++++++++++ src/spaces/homspace.jl | 2 +- src/spaces/productspace.jl | 4 ++-- src/spaces/vectorspaces.jl | 14 +------------- src/tensors/tensor.jl | 34 +++++++++------------------------- test/spaces.jl | 6 +++--- 7 files changed, 36 insertions(+), 44 deletions(-) create mode 100644 src/auxiliary/iterators.jl diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 2ccb3e49..befa1a31 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -121,6 +121,7 @@ import Base.Meta #----------------- include("auxiliary/auxiliary.jl") include("auxiliary/dicts.jl") +include("auxiliary/iterators.jl") include("auxiliary/linalg.jl") include("auxiliary/random.jl") diff --git a/src/auxiliary/iterators.jl b/src/auxiliary/iterators.jl new file mode 100644 index 00000000..fdf6c34a --- /dev/null +++ b/src/auxiliary/iterators.jl @@ -0,0 +1,19 @@ +struct OneOrNoneIterator{T} + cond::Bool + first::T +end + +function Base.iterate(it::OneOrNoneIterator, state=true) + if state && it.cond + return (it.first, false) + else + return nothing + end +end + +Base.IteratorEltype(::Type{<:OneOrNoneIterator}) = Base.HasEltype() +Base.IteratorSize(::Type{<:OneOrNoneIterator}) = Base.HasLength() + +Base.isempty(it::OneOrNoneIterator) = !it.cond +Base.length(it::OneOrNoneIterator) = Int(it.cond) +Base.eltype(::OneOrNoneIterator{T}) where {T} = T diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 67083231..2368ddc6 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -72,7 +72,7 @@ See also [`hasblock`](@ref). """ function blocksectors(W::HomSpace) sectortype(W) === Trivial && - return TrivialOrEmptyIterator(dim(domain(W)) == 0 || dim(codomain(W)) == 0) + return OneOrNoneIterator(dim(domain(W)) != 0 && dim(codomain(W)) != 0, Trivial()) codom = codomain(W) dom = domain(W) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index e4630765..bdf80c50 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -59,7 +59,7 @@ Return an iterator over all possible combinations of sectors (represented as an """ sectors(P::ProductSpace) = _sectors(P, sectortype(P)) _sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{Trivial}) where {N} = - (ntuple(n->Trivial(), N),) # speed up sectors for ungraded spaces + OneOrNoneIterator(dim(P) != 0, ntuple(n->Trivial(), N)) _sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{<:Sector}) where {N} = product(map(sectors, P.spaces)...) @@ -106,7 +106,7 @@ that make up the `ProductSpace` instance. function blocksectors(P::ProductSpace{S, N}) where {S, N} I = sectortype(S) if I == Trivial - return TrivialOrEmptyIterator(dim(P) == 0) + return OneOrNoneIterator(dim(P) != 0, Trivial()) end bs = Vector{I}() if N == 0 diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index f0def932..9c26a4cd 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -212,24 +212,12 @@ dimension, i.e. `dim(V, a) > 0`. hassector(V::ElementarySpace, ::Trivial) = dim(V) != 0 Base.axes(V::ElementarySpace, ::Trivial) = axes(V) -struct TrivialOrEmptyIterator - isempty::Bool -end -Base.IteratorSize(::TrivialOrEmptyIterator) = Base.HasLength() -Base.IteratorEltype(::TrivialOrEmptyIterator) = Base.HasEltype() -Base.isempty(V::TrivialOrEmptyIterator) = V.isempty -Base.length(V::TrivialOrEmptyIterator) = isempty(V) ? 0 : 1 -Base.eltype(::TrivialOrEmptyIterator) = Trivial -function Base.iterate(V::TrivialOrEmptyIterator, state = true) - return isempty(V) == state ? nothing : (Trivial(), false) -end - """ sectors(V::ElementarySpace) Return an iterator over the different sectors of `V`. """ -sectors(V::ElementarySpace) = TrivialOrEmptyIterator(dim(V) == 0) +sectors(V::ElementarySpace) = OneOrNoneIterator(dim(V) != 0, Trivial()) dim(V::ElementarySpace, ::Trivial) = sectortype(V) == Trivial ? dim(V) : throw(SectorMismatch()) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 4bf24646..7e4c0846 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -60,7 +60,7 @@ tensormaptype(S, N₁, N₂ = 0) = tensormaptype(S, N₁, N₂, Float64) codomain(t::TensorMap) = t.codom domain(t::TensorMap) = t.dom -blocksectors(t::TrivialTensorMap) = TrivialOrEmptyIterator(dim(t) == 0) +blocksectors(t::TrivialTensorMap) = OneOrNoneIterator(dim(t) != 0, Trivial()) blocksectors(t::TensorMap) = keys(t.data) storagetype(::Type{<:TensorMap{<:IndexSpace,N₁,N₂,Trivial,A}}) where @@ -88,14 +88,14 @@ function TensorMap(data::DenseArray, codom::ProductSpace{S,N₁}, dom::ProductSp t = TensorMap(zeros, eltype(data), codom, dom) ta = convert(Array, t) l = length(ta) - basis = zeros(eltype(ta), (l, dim(t))) - qdims = zeros(real(eltype(ta)), (dim(t),)) + dimt = dim(t) + basis = zeros(eltype(ta), (l, dimt)) + qdims = zeros(real(eltype(ta)), (dimt,)) i = 1 for (c,b) in blocks(t) for k = 1:length(b) b[k] = 1 - copyto!(view(basis, :, i), reshape(convert(Array, t), (l,))) - # TODO: change this to `copy!` once we drop support for Julia 1.4 + copy!(view(basis, :, i), reshape(convert(Array, t), (l,))) qdims[i] = dim(c) b[k] = 0 i += 1 @@ -142,13 +142,7 @@ function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpa colr = SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}}() rowdims = SectorDict{I, Int}() coldims = SectorDict{I, Int}() - if N₁ == 0 || N₂ == 0 - blocksectoriterator = (one(I),) - elseif N₂ <= N₁ - blocksectoriterator = blocksectors(dom) - else - blocksectoriterator = blocksectors(codom) - end + blocksectoriterator = blocksectors(codom ← dom) for s1 in sectors(codom) for c in blocksectoriterator offset1 = get!(rowdims, c, 0) @@ -228,13 +222,7 @@ function TensorMap(f, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) wh colr = SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}}() rowdims = SectorDict{I, Int}() coldims = SectorDict{I, Int}() - if N₁ == 0 || N₂ == 0 - blocksectoriterator = (one(I),) - elseif N₂ <= N₁ - blocksectoriterator = blocksectors(dom) - else - blocksectoriterator = blocksectors(codom) - end + blocksectoriterator = blocksectors(codom ← dom) for s1 in sectors(codom) for c in blocksectoriterator offset1 = get!(rowdims, c, 0) @@ -337,12 +325,8 @@ Tensor(P::TensorSpace{S}) where {S<:IndexSpace} = TensorMap(P, one(P)) # Efficient copy constructors #----------------------------- -function Base.copy(t::TrivialTensorMap{S, N₁, N₂, A}) where {S, N₁, N₂, A} - return TrivialTensorMap{S, N₁, N₂, A}(copy(t.data), t.codom, t.dom) -end -function Base.copy(t::TensorMap{S, N₁, N₂, I, A, F₁, F₂}) where {S, N₁, N₂, I, A, F₁, F₂} - return TensorMap{S, N₁, N₂, I, A, F₁, F₂}(deepcopy(t.data), t.codom, t.dom, t.rowr, t.colr) -end +Base.copy(t::TrivialTensorMap) = typeof(t)(copy(t.data), t.codom, t.dom) +Base.copy(t::TensorMap) = typeof(t)(deepcopy(t.data), t.codom, t.dom, t.rowr, t.colr) # Similar #--------- diff --git a/test/spaces.jl b/test/spaces.jl index 3a56fe60..6a23e903 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -288,8 +288,7 @@ end @test @constinferred(dims(P)) == map(dim, (V1, V2, V3, V4)) @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3, V4)) @test @constinferred(dim(P, 2)) == dim(V2) - @test @constinferred(sectors(P)) == - (mapreduce(sectors, (a, b) -> tuple(a..., b...), (V1, V2, V3, V4)),) + @test first(@constinferred(sectors(P))) == (Trivial(), Trivial(), Trivial(), Trivial()) cube(x) = x^3 @test @constinferred(cube(V1)) == V1 ⊗ V1 ⊗ V1 N = 3 @@ -297,7 +296,8 @@ end @test P^2 == P ⊗ P @test @constinferred(dims(P, first(sectors(P)))) == dims(P) @test ((@constinferred blocksectors(P))...,) == (Trivial(),) - @test (blocksectors(P ⊗ ℂ^0)...,) == () + @test isempty(blocksectors(P ⊗ ℂ^0)) + @test isempty(@constinferred(sectors(P ⊗ ℂ^0))) @test @constinferred(blockdim(P, first(blocksectors(P)))) == dim(P) @test Base.IteratorEltype(P) == Base.IteratorEltype(typeof(P)) == Base.IteratorEltype(P.spaces) From 34acb3353585da86baeec445f905cc156c9ef524 Mon Sep 17 00:00:00 2001 From: Jutho Date: Wed, 26 Jul 2023 14:41:04 +0200 Subject: [PATCH 23/57] revise TensorMap constructor and --- src/TensorKit.jl | 4 +- src/sectors/sectors.jl | 2 +- src/tensors/adjoint.jl | 2 + src/tensors/tensor.jl | 377 +++++++++++++++++++++-------------------- 4 files changed, 199 insertions(+), 186 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index befa1a31..18a33dd6 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -141,7 +141,7 @@ struct SectorMismatch{S<:Union{Nothing, String}} <: TensorException end SectorMismatch()=SectorMismatch{Nothing}(nothing) Base.show(io::IO, ::SectorMismatch{Nothing}) = print(io, "SectorMismatch()") -Base.show(io::IO, e::SectorMismatch) = print(io, "SectorMismatch(", e.message, ")") +Base.show(io::IO, e::SectorMismatch) = print(io, "SectorMismatch(\"", e.message, "\")") # Exception type for all errors related to vector space mismatch struct SpaceMismatch{S<:Union{Nothing, String}} <: TensorException @@ -149,7 +149,7 @@ struct SpaceMismatch{S<:Union{Nothing, String}} <: TensorException end SpaceMismatch()=SpaceMismatch{Nothing}(nothing) Base.show(io::IO, ::SpaceMismatch{Nothing}) = print(io, "SpaceMismatch()") -Base.show(io::IO, e::SpaceMismatch) = print(io, "SpaceMismatch(", e.message, ")") +Base.show(io::IO, e::SpaceMismatch) = print(io, "SpaceMismatch(\"", e.message, "\")") # Exception type for all errors related to invalid tensor index specification. struct IndexError{S<:Union{Nothing, String}} <: TensorException diff --git a/src/sectors/sectors.jl b/src/sectors/sectors.jl index 62489894..093ce1aa 100644 --- a/src/sectors/sectors.jl +++ b/src/sectors/sectors.jl @@ -135,7 +135,7 @@ Nsymbol(::Trivial, ::Trivial, ::Trivial) = true # trait to describe the fusion of superselection sectors abstract type FusionStyle end -struct UniqueFusion <: FusionStyle # unique fusion output when fusion two sectors +struct UniqueFusion <: FusionStyle # unique fusion output when fusing two sectors end abstract type MultipleFusion <: FusionStyle end struct SimpleFusion <: MultipleFusion # multiple fusion but multiplicity free diff --git a/src/tensors/adjoint.jl b/src/tensors/adjoint.jl index a9c041f2..b1654542 100644 --- a/src/tensors/adjoint.jl +++ b/src/tensors/adjoint.jl @@ -18,6 +18,8 @@ const AdjointTrivialTensorMap{S<:IndexSpace, N₁, N₂, A<:DenseMatrix} = Base.adjoint(t::TensorMap) = AdjointTensorMap(t) Base.adjoint(t::AdjointTensorMap) = t.parent +Base.similar(t::AdjointTensorMap, T::Type, P::TensorMapSpace) = similar(t', T, P) + # Properties codomain(t::AdjointTensorMap) = domain(t.parent) domain(t::AdjointTensorMap) = codomain(t.parent) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 7e4c0846..490cc1e0 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -72,61 +72,9 @@ dim(t::TensorMap) = mapreduce(x->length(x[2]), +, blocks(t); init = 0) # General TensorMap constructors #-------------------------------- -# with data -function TensorMap(data::DenseArray, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}; - tol = sqrt(eps(real(float(eltype(data)))))) where {S<:IndexSpace, N₁, N₂} - (d1, d2) = (dim(codom), dim(dom)) - if !(length(data) == d1*d2 || size(data) == (d1, d2) || - size(data) == (dims(codom)..., dims(dom)...)) - throw(DimensionMismatch()) - end - if sectortype(S) === Trivial - data2 = reshape(data, (d1, d2)) - A = typeof(data2) - return TensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data2, codom, dom) - else - t = TensorMap(zeros, eltype(data), codom, dom) - ta = convert(Array, t) - l = length(ta) - dimt = dim(t) - basis = zeros(eltype(ta), (l, dimt)) - qdims = zeros(real(eltype(ta)), (dimt,)) - i = 1 - for (c,b) in blocks(t) - for k = 1:length(b) - b[k] = 1 - copy!(view(basis, :, i), reshape(convert(Array, t), (l,))) - qdims[i] = dim(c) - b[k] = 0 - i += 1 - end - end - rhs = reshape(data, (l,)) - if FusionStyle(sectortype(t)) isa UniqueFusion - lhs = basis'*rhs - else - lhs = Diagonal(qdims) \ (basis'*rhs) - end - if norm(basis*lhs - rhs) > tol - throw(ArgumentError("Data has non-zero elements at incompatible positions")) - end - if eltype(lhs) != scalartype(t) - t2 = TensorMap(zeros, promote_type(eltype(lhs), scalartype(t)), codom, dom) - else - t2 = t - end - i = 1 - for (c,b) in blocks(t2) - for k = 1:length(b) - b[k] = lhs[i] - i += 1 - end - end - return t2 - end -end - -function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) where {S<:IndexSpace, N₁, N₂} +# constructor starting from block data +function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpace{S,N₁}, + dom::ProductSpace{S,N₂}) where {S<:IndexSpace,N₁,N₂} I = sectortype(S) I == keytype(data) || throw(SectorMismatch()) if I == Trivial @@ -136,133 +84,78 @@ function TensorMap(data::AbstractDict{<:Sector,<:DenseMatrix}, codom::ProductSpa return TensorMap(valtype(data)(undef, dim(codom), dim(dom)), codom, dom) end end - F₁ = fusiontreetype(I, N₁) - F₂ = fusiontreetype(I, N₂) - rowr = SectorDict{I, FusionTreeDict{F₁, UnitRange{Int}}}() - colr = SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}}() - rowdims = SectorDict{I, Int}() - coldims = SectorDict{I, Int}() blocksectoriterator = blocksectors(codom ← dom) - for s1 in sectors(codom) - for c in blocksectoriterator - offset1 = get!(rowdims, c, 0) - rowrc = get!(rowr, c) do - FusionTreeDict{F₁, UnitRange{Int}}() - end - for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) - r = (offset1 + 1):(offset1 + dim(codom, s1)) - push!(rowrc, f₁ => r) - offset1 = last(r) - end - rowdims[c] = offset1 - end - end - for s2 in sectors(dom) - for c in blocksectoriterator - offset2 = get!(coldims, c, 0) - colrc = get!(colr, c) do - FusionTreeDict{F₂, UnitRange{Int}}() - end - for f₂ in fusiontrees(s2, c, map(isdual, dom.spaces)) - r = (offset2 + 1):(offset2 + dim(dom, s2)) - push!(colrc, f₂ => r) - offset2 = last(r) - end - coldims[c] = offset2 - end - end for c in blocksectoriterator - dim1 = get!(rowdims, c, 0) - dim2 = get!(coldims, c, 0) - if dim1 == 0 || dim2 == 0 - delete!(rowr, c) - delete!(colr, c) - else - (haskey(data, c) && size(data[c]) == (dim1, dim2)) || - throw(DimensionMismatch()) - end + haskey(data, c) || throw(SectorMismatch("no data for block sector $c")) end - if !isreal(I) && eltype(valtype(data)) <: Real - b = valtype(data)(undef, (0,0)) - V = typeof(complex(b)) - K = keytype(data) - data2 = SectorDict{K,V}((c=>complex(data[c])) for c in keys(rowr)) + rowr, rowdims = _buildblockstructure(codom, blocksectoriterator) + colr, coldims = _buildblockstructure(dom, blocksectoriterator) + for (c, b) in data + c in blocksectoriterator || isempty(b) || + throw(SectorMismatch("data for block sector $c not expected")) + isempty(b) || size(b) == (rowdims[c], coldims[c]) || + throw(DimensionMismatch("wrong size of block for sector $c")) + end + F₁ = fusiontreetype(I, N₁) + F₂ = fusiontreetype(I, N₂) + if !isreal(I) + data2 = SectorDict(c => complex(data[c]) for c in blocksectoriterator) A = typeof(data2) - return TensorMap{S, N₁, N₂, I, A, F₁, F₂}(data2, codom, dom, rowr, colr) + return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data2, codom, dom, rowr, colr) else - V = valtype(data) - K = keytype(data) - data2 = SectorDict{K,V}((c=>data[c]) for c in keys(rowr)) + data2 = SectorDict(c => data[c] for c in blocksectoriterator) A = typeof(data2) - return TensorMap{S, N₁, N₂, I, A, F₁, F₂}(data2, codom, dom, rowr, colr) + return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data2, codom, dom, rowr, colr) end end -# without data: generic constructor from callable: -function TensorMap(f, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) where {S<:IndexSpace, N₁, N₂} +# constructor from general callable that produces block data +function TensorMap(f, codom::ProductSpace{S,N₁}, + dom::ProductSpace{S,N₂}) where {S<:IndexSpace,N₁,N₂} I = sectortype(S) if I == Trivial d1 = dim(codom) d2 = dim(dom) - data = f((d1,d2)) + data = f((d1, d2)) A = typeof(data) - return TensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data, codom, dom) + return TensorMap{S,N₁,N₂,Trivial,A,Nothing,Nothing}(data, codom, dom) + end + blocksectoriterator = blocksectors(codom ← dom) + rowr, rowdims = _buildblockstructure(codom, blocksectoriterator) + colr, coldims = _buildblockstructure(dom, blocksectoriterator) + if !isreal(I) + data = SectorDict(c => complex(f((rowdims[c], coldims[c]))) + for c in blocksectoriterator) else - F₁ = fusiontreetype(I, N₁) - F₂ = fusiontreetype(I, N₂) - # TODO: the current approach is not very efficient and somewhat wasteful - sampledata = f((1,1)) - if !isreal(I) && eltype(sampledata) <: Real - A = typeof(complex(sampledata)) - else - A = typeof(sampledata) - end - data = SectorDict{I,A}() - rowr = SectorDict{I, FusionTreeDict{F₁, UnitRange{Int}}}() - colr = SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}}() - rowdims = SectorDict{I, Int}() - coldims = SectorDict{I, Int}() - blocksectoriterator = blocksectors(codom ← dom) - for s1 in sectors(codom) - for c in blocksectoriterator - offset1 = get!(rowdims, c, 0) - rowrc = get!(rowr, c) do - FusionTreeDict{F₁, UnitRange{Int}}() - end - for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) - r = (offset1 + 1):(offset1 + dim(codom, s1)) - push!(rowrc, f₁ => r) - offset1 = last(r) - end - rowdims[c] = offset1 + data = SectorDict(c => f((rowdims[c], coldims[c])) for c in blocksectoriterator) + end + F₁ = fusiontreetype(I, N₁) + F₂ = fusiontreetype(I, N₂) + A = typeof(data) + return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data, codom, dom, rowr, colr) +end + +# auxiliary function +function _buildblockstructure(P::ProductSpace{S,N}, blocksectors) where {S<:IndexSpace,N} + I = sectortype(S) + F = fusiontreetype(I, N) + treeranges = SectorDict{I,FusionTreeDict{F,UnitRange{Int}}}() + blockdims = SectorDict{I,Int}() + for s in sectors(P) + for c in blocksectors + offset = get!(blockdims, c, 0) + treerangesc = get!(treeranges, c) do + return FusionTreeDict{F,UnitRange{Int}}() end - end - for s2 in sectors(dom) - for c in blocksectoriterator - offset2 = get!(coldims, c, 0) - colrc = get!(colr, c) do - FusionTreeDict{F₂, UnitRange{Int}}() - end - for f₂ in fusiontrees(s2, c, map(isdual, dom.spaces)) - r = (offset2 + 1):(offset2 + dim(dom, s2)) - push!(colrc, f₂ => r) - offset2 = last(r) - end - coldims[c] = offset2 + for f in fusiontrees(s, c, map(isdual, P.spaces)) + r = (offset + 1):(offset + dim(P, s)) + push!(treerangesc, f => r) + offset = last(r) end + blockdims[c] = offset end - for c in blocksectoriterator - dim1 = get!(rowdims, c, 0) - dim2 = get!(coldims, c, 0) - if dim1 == 0 || dim2 == 0 - delete!(rowr, c) - delete!(colr, c) - else - data[c] = f((dim1, dim2)) - end - end - return TensorMap{S, N₁, N₂, I, SectorDict{I,A}, F₁, F₂}(data, codom, dom, rowr, colr) end + return treeranges, blockdims end TensorMap(f, @@ -323,6 +216,60 @@ Tensor(T::Type{<:Number}, P::TensorSpace{S}) where {S<:IndexSpace} = TensorMap(T Tensor(P::TensorSpace{S}) where {S<:IndexSpace} = TensorMap(P, one(P)) +# constructor starting from a dense array +function TensorMap(data::DenseArray, codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}; + tol=sqrt(eps(real(float(eltype(data)))))) where {S<:IndexSpace,N₁,N₂} + (d1, d2) = (dim(codom), dim(dom)) + if !(length(data) == d1 * d2 || size(data) == (d1, d2) || + size(data) == (dims(codom)..., dims(dom)...)) + throw(DimensionMismatch()) + end + if sectortype(S) === Trivial + data2 = reshape(data, (d1, d2)) + A = typeof(data2) + return TensorMap{S,N₁,N₂,Trivial,A,Nothing,Nothing}(data2, codom, dom) + else + t = TensorMap(zeros, eltype(data), codom, dom) + ta = convert(Array, t) + l = length(ta) + dimt = dim(t) + basis = zeros(eltype(ta), (l, dimt)) + qdims = zeros(real(eltype(ta)), (dimt,)) + i = 1 + for (c, b) in blocks(t) + for k in 1:length(b) + b[k] = 1 + copy!(view(basis, :, i), reshape(convert(Array, t), (l,))) + qdims[i] = dim(c) + b[k] = 0 + i += 1 + end + end + rhs = reshape(data, (l,)) + if FusionStyle(sectortype(t)) isa UniqueFusion + lhs = basis' * rhs + else + lhs = Diagonal(qdims) \ (basis' * rhs) + end + if norm(basis * lhs - rhs) > tol + throw(ArgumentError("Data has non-zero elements at incompatible positions")) + end + if eltype(lhs) != scalartype(t) + t2 = TensorMap(zeros, promote_type(eltype(lhs), scalartype(t)), codom, dom) + else + t2 = t + end + i = 1 + for (c, b) in blocks(t2) + for k in 1:length(b) + b[k] = lhs[i] + i += 1 + end + end + return t2 + end +end + # Efficient copy constructors #----------------------------- Base.copy(t::TrivialTensorMap) = typeof(t)(copy(t.data), t.codom, t.dom) @@ -330,30 +277,94 @@ Base.copy(t::TensorMap) = typeof(t)(deepcopy(t.data), t.codom, t.dom, t.rowr, t. # Similar #--------- -Base.similar(t::AbstractTensorMap, T::Type, codomain::VectorSpace, domain::VectorSpace) = - similar(t, T, codomain←domain) -Base.similar(t::AbstractTensorMap, codomain::VectorSpace, domain::VectorSpace) = - similar(t, codomain←domain) - -Base.similar(t::AbstractTensorMap{S}, ::Type{T}, - P::TensorMapSpace{S} = (domain(t) → codomain(t))) where {T,S} = - TensorMap(d->similarstoragetype(t, T)(undef, d), P) -Base.similar(t::AbstractTensorMap{S}, ::Type{T}, P::TensorSpace{S}) where {T,S} = - Tensor(d->similarstoragetype(t, T)(undef, d), P) -Base.similar(t::AbstractTensorMap{S}, - P::TensorMapSpace{S} = (domain(t) → codomain(t))) where {S} = - TensorMap(d->storagetype(t)(undef, d), P) -Base.similar(t::AbstractTensorMap{S}, P::TensorSpace{S}) where {S} = - Tensor(d->storagetype(t)(undef, d), P) +# 4 arguments +function Base.similar(t::AbstractTensorMap, T::Type, codomain::VectorSpace, + domain::VectorSpace) + return similar(t, T, codomain ← domain) +end +# 3 arguments +function Base.similar(t::AbstractTensorMap, codomain::VectorSpace, domain::VectorSpace) + return similar(t, scalartype(t), codomain ← domain) +end +function Base.similar(t::AbstractTensorMap, T::Type, codomain::VectorSpace) + return similar(t, T, codomain ← one(codomain)) +end +# 2 arguments +function Base.similar(t::AbstractTensorMap, codomain::VectorSpace) + return similar(t, scalartype(t), codomain ← one(codomain)) +end +Base.similar(t::AbstractTensorMap, P::TensorMapSpace) = similar(t, scalartype(t), P) +Base.similar(t::AbstractTensorMap, T::Type) = similar(t, T, space(t)) +# 1 argument +Base.similar(t::AbstractTensorMap) = similar(t, scalartype(t), space(t)) + +# actual implementation +function Base.similar(t::TensorMap{S}, ::Type{T}, P::TensorMapSpace{S}) where {T,S} + N₁ = length(codomain(P)) + N₂ = length(domain(P)) + I = sectortype(S) + # speed up specialized cases + if I === Trivial + data = similar(t.data, T, (dim(codomain(P)), dim(domain(P)))) + A = typeof(data) + return TrivialTensorMap{S,N₁,N₂,A}(data, codomain(P), domain(P)) + end + F₁ = fusiontreetype(I, N₁) + F₂ = fusiontreetype(I, N₂) + if space(t) == P + data = SectorDict(c => similar(b, T) for (c, b) in blocks(t)) + A = typeof(data) + return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data, codomain(P), domain(P), t.rowr, t.colr) + end + + blocksectoriterator = blocksectors(P) + # try to recycle rowr + if codomain(P) == codomain(t) && all(c -> haskey(t.rowr, c), blocksectoriterator) + if length(t.rowr) == length(blocksectoriterator) + rowr = t.rowr + else + rowr = SectorDict(c => t.rowr[c] for c in blocksectoriterator) + end + rowdims = SectorDict(c => size(block(t, c), 1) for c in blocksectoriterator) + elseif codomain(P) == domain(t) && all(c -> haskey(t.colr, c), blocksectoriterator) + if length(t.colr) == length(blocksectoriterator) + rowr = t.colr + else + rowr = SectorDict(c => t.colr[c] for c in blocksectoriterator) + end + rowdims = SectorDict(c => size(block(t, c), 2) for c in blocksectoriterator) + else + rowr, rowdims = _buildblockstructure(codomain(P), blocksectoriterator) + end + # try to recylce colr + if domain(P) == codomain(t) && all(c -> haskey(t.rowr, c), blocksectoriterator) + if length(t.rowr) == length(blocksectoriterator) + colr = t.rowr + else + colr = SectorDict(c => t.rowr[c] for c in blocksectoriterator) + end + coldims = SectorDict(c => size(block(t, c), 1) for c in blocksectoriterator) + elseif domain(P) == domain(t) && all(c -> haskey(t.colr, c), blocksectoriterator) + if length(t.colr) == length(blocksectoriterator) + colr = t.colr + else + colr = SectorDict(c => t.colr[c] for c in blocksectoriterator) + end + coldims = SectorDict(c => size(block(t, c), 2) for c in blocksectoriterator) + else + colr, coldims = _buildblockstructure(domain(P), blocksectoriterator) + end + M = similarstoragetype(t, T) + data = SectorDict{I,M}(c => M(undef, (rowdims[c], coldims[c])) for c in blocksectoriterator) + A = typeof(data) + return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data, codomain(P), domain(P), rowr, colr) +end function Base.complex(t::AbstractTensorMap) if scalartype(t) <: Complex return t - elseif t.data isa AbstractArray - return TensorMap(complex(t.data), codomain(t), domain(t)) else - data = SectorDict(c=>complex(d) for (c,d) in t.data) - return TensorMap(data, codomain(t), domain(t)) + return copy!(similar(t, complex(scalartype(t))), t) end end @@ -386,7 +397,7 @@ end # Getting and setting the data #------------------------------ -hasblock(t::TrivialTensorMap, ::Trivial) = true +hasblock(t::TrivialTensorMap, ::Trivial) = !isempty(t.data) hasblock(t::TensorMap, s::Sector) = haskey(t.data, s) block(t::TrivialTensorMap, ::Trivial) = t.data From d5230be41e77b000e86684b0a2d8e1406e666862 Mon Sep 17 00:00:00 2001 From: Jutho Date: Wed, 26 Jul 2023 16:57:45 +0200 Subject: [PATCH 24/57] implement vectorinterface support --- src/tensors/linalg.jl | 62 +++++------------------- src/tensors/vectorinterface.jl | 86 ++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 7472a72b..fd8b5c90 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -150,11 +150,6 @@ end # In-place methods #------------------ -import Base: copyto! -Base.@deprecate( - copyto!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap), - copy!(tdst, tsrc)) - # Wrapping the blocks in a StridedView enables multithreading if JULIA_NUM_THREADS > 1 # Copy, adjoint! and fill: function Base.copy!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) @@ -181,52 +176,21 @@ function LinearAlgebra.adjoint!(tdst::AbstractTensorMap, return tdst end -# Basic vector space methods: addition and scalar multiplication -LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) = mul!(t, t, α) -LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) = mul!(t, α, t) +# Basic vector space methods: recycle VectorInterface implementation +LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) = scale!(t, α) +LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) = scale!(t, α) -function LinearAlgebra.mul!(t1::AbstractTensorMap, t2::AbstractTensorMap, α::Number) - space(t1) == space(t2) || throw(SpaceMismatch()) - for c in blocksectors(t1) - mul!(StridedView(block(t1, c)), StridedView(block(t2, c)), α) - end - return t1 -end -function LinearAlgebra.mul!(t1::AbstractTensorMap, α::Number, t2::AbstractTensorMap) - space(t1) == space(t2) || throw(SpaceMismatch()) - for c in blocksectors(t1) - mul!(StridedView(block(t1, c)), α, StridedView(block(t2, c))) - end - return t1 -end -function LinearAlgebra.axpy!(α::Number, t1::AbstractTensorMap, t2::AbstractTensorMap) - space(t1) == space(t2) || throw(SpaceMismatch()) - for c in blocksectors(t1) - axpy!(α, StridedView(block(t1, c)), StridedView(block(t2, c))) - end - return t2 -end -function LinearAlgebra.axpby!(α::Number, t1::AbstractTensorMap, - β::Number, t2::AbstractTensorMap) - space(t1) == space(t2) || throw(SpaceMismatch()) - for c in blocksectors(t1) - axpby!(α, StridedView(block(t1, c)), β, StridedView(block(t2, c))) - end - return t2 -end +LinearAlgebra.mul!(t1::AbstractTensorMap, t2::AbstractTensorMap, α::Number) = scale!(t1, t2, α) +LinearAlgebra.mul!(t1::AbstractTensorMap, α::Number, t2::AbstractTensorMap) = scale!(t1, t2, α) + +# TODO: remove VectorInterface namespace when we renamed TensorKit.add! +LinearAlgebra.axpy!(α::Number, t1::AbstractTensorMap, t2::AbstractTensorMap) = + VectorInterface.add!(t2, t1, α) +LinearAlgebra.axpby!(α::Number, t1::AbstractTensorMap, β::Number, t2::AbstractTensorMap) = + VectorInterface.add!(t2, t1, α, β) # inner product and norm only valid for spaces with Euclidean inner product -function LinearAlgebra.dot(t1::AbstractTensorMap, t2::AbstractTensorMap) - space(t1) == space(t2) || throw(SpaceMismatch()) - InnerProductStyle(t1) === EuclideanProduct() || - throw(ArgumentError("dot requires Euclidean inner product")) - T = promote_type(scalartype(t1), scalartype(t2)) - s = zero(T) - for c in blocksectors(t1) - s += convert(T, dim(c)) * dot(block(t1, c), block(t2, c)) - end - return s -end +LinearAlgebra.dot(t1::AbstractTensorMap, t2::AbstractTensorMap) = inner(t1, t2) function LinearAlgebra.norm(t::AbstractTensorMap, p::Real = 2) InnerProductStyle(t) === EuclideanProduct() || @@ -264,7 +228,7 @@ function LinearAlgebra.tr(t::AbstractTensorMap) return sum(dim(c)*tr(b) for (c, b) in blocks(t)) end -# TensorMap multiplication: +# TensorMap multiplication function LinearAlgebra.mul!(tC::AbstractTensorMap, tA::AbstractTensorMap, tB::AbstractTensorMap, α = true, β = false) diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index ecc17c16..315e0a9e 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -1 +1,87 @@ +using VectorInterface: ONumber, _one +_isone(α::ONumber) = α == _one || isone(α) + +# scalartype +#------------ VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = scalartype(storagetype(T)) + +# zerovector & zerovector!! +#--------------------------- +function VectorInterface.zerovector(t::AbstractTensorMap, ::Type{S}) where {S<:Number} + return zero!(similar(t, S)) +end +VectorInterface.zerovector!(t::AbstractTensorMap) = zero!(t) +VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) + +# scale, scale! & scale!! +#------------------------- +VectorInterface.scale(t::TensorMap, α::ONumber) = _isone(α) ? t : t * α +function VectorInterface.scale!(t::AbstractTensorMap, α::ONumber) + for (c,b) in blocks(t) + scale!(b, α) + end + return t +end +function VectorInterface.scale!!(t::AbstractTensorMap, α::ONumber) + _isone(α) && return t + if promote_type(scalartype(t), typeof(α)) <: scalartype(t) + return scale!(t, α) + else + return scale(t, α) + end +end + +function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber) + space(ty) == space(tx) || throw(SpaceMismatch()) + for c in blocksectors(tx) + scale!(block(ty, c), block(tx, c), α) + end + return ty +end +function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber) + space(ty) == space(tx) || throw(SpaceMismatch()) + T = scalartype(ty) + if promote_type(T, typeof(α), scalartype(tx)) <: T + return scale!(ty, tx, α) + else + return scale(tx, α) + end +end + +# add, add! & add!! +#------------------- +# TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed +function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) + space(ty) == space(tx) || throw(SpaceMismatch()) + T = promote_type(scalartype(ty), scalartype(tx), typeof(α), typeof(β)) + return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) +end +function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) + space(ty) == space(tx) || throw(SpaceMismatch()) + for c in blocksectors(tx) + VectorInterface.add!(block(ty, c), block(tx, c), α, β) + end + return ty +end +function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) + T = scalartype(ty) + if promote_type(T, typeof(α), typeof(β), scalartype(tx)) <: T + return VectorInterface.add!(ty, tx, α, β) + else + return VectorInterface.add(ty, tx, α, β) + end +end + +# inner +#------- +function VectorInterface.inner(tx::AbstractTensorMap, ty::AbstractTensorMap) + space(tx) == space(ty) || throw(SpaceMismatch()) + InnerProductStyle(tx) === EuclideanProduct() || + throw(ArgumentError("dot requires Euclidean inner product")) + T = promote_type(scalartype(tx), scalartype(ty)) + s = zero(T) + for c in blocksectors(tx) + s += convert(T, dim(c)) * dot(block(tx, c), block(ty, c)) + end + return s +end \ No newline at end of file From 8b9bd7213d61fa3d54c7e64a5a13c325eae010b7 Mon Sep 17 00:00:00 2001 From: Jutho Date: Thu, 27 Jul 2023 17:02:32 +0200 Subject: [PATCH 25/57] try new formatting with TestExtras v0.2 in tests --- test/fusiontrees.jl | 14 +- test/sectors.jl | 199 ++++++------ test/spaces.jl | 767 ++++++++++++++++++++++---------------------- test/tensors.jl | 8 +- 4 files changed, 488 insertions(+), 500 deletions(-) diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index f2840399..af5aeaf5 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -2,7 +2,7 @@ println("------------------------------------") println("Fusion Trees") println("------------------------------------") ti = time() -@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" for I in sectorlist +@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose=true for I in sectorlist Istr = TensorKit.type_repr(I) N = 5 out = ntuple(n -> randsector(I), N) @@ -17,7 +17,7 @@ ti = time() it = @constinferred fusiontrees(out, in, isdual) @constinferred Nothing iterate(it) f = @constinferred first(it) - @testset "Fusion tree $I: printing" begin + @testset "Fusion tree $Istr: printing" begin @test eval(Meta.parse(sprint(show, f))) == f end @testset "Fusion tree $Istr: insertat" begin @@ -42,13 +42,13 @@ ti = time() @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1) levels = ntuple(identity, N) - gen = Base.Generator(braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...))) do (t, - c) - (t′, c′) = first(TK.insertat(t, 1, f2)) + function _reinsert_partial_tree(t, f) + (t′, c′) = first(TK.insertat(t, 1, f)) @test c′ == one(c′) - return t′ => c + return t′ end - trees2 = Dict(gen) + braid_i_to_1 = braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...)) + trees2 = Dict(_reinsert_partial_tree(t,f2)=>c for (t,c) in braid_i_to_1) trees3 = empty(trees2) p = (((N + 1):(N + i - 1))..., (1:N)..., ((N + i):(2N - 1))...) levels = ((i:(N + i - 1))..., (1:(i - 1))..., ((i + N):(2N - 1))...) diff --git a/test/sectors.jl b/test/sectors.jl index cfa548fa..e957fc38 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -1,123 +1,118 @@ println("------------------------------------") println("Sectors") println("------------------------------------") -ti = time() - -@timedtestset "Sector properties of $(TensorKit.type_repr(I))" for I in sectorlist - Istr = TensorKit.type_repr(I) - @testset "Sector $Istr: Basic properties" begin - s = (randsector(I), randsector(I), randsector(I)) - @test eval(Meta.parse(sprint(show, I))) == I - @test eval(Meta.parse(TensorKit.type_repr(I))) == I - @test eval(Meta.parse(sprint(show, s[1]))) == s[1] - @test @constinferred(hash(s[1])) == hash(deepcopy(s[1])) - @test @constinferred(one(s[1])) == @constinferred(one(I)) - @constinferred dual(s[1]) - @constinferred dim(s[1]) - @constinferred frobeniusschur(s[1]) - @constinferred Nsymbol(s...) - @constinferred Rsymbol(s...) - @constinferred Bsymbol(s...) - @constinferred Fsymbol(s..., s...) - it = @constinferred s[1] ⊗ s[2] - @constinferred ⊗(s..., s...) - end - @testset "Sector $Istr: Value iterator" begin - @test eltype(values(I)) == I - sprev = one(I) - for (i, s) in enumerate(values(I)) - @test !isless(s, sprev) # confirm compatibility with sort order +@timedtestset "Sectors" verbose = true begin + @timedtestset "Sector properties of $(TensorKit.type_repr(I))" for I in sectorlist + Istr = TensorKit.type_repr(I) + @testset "Sector $Istr: Basic properties" begin + s = (randsector(I), randsector(I), randsector(I)) + @test eval(Meta.parse(sprint(show, I))) == I + @test eval(Meta.parse(TensorKit.type_repr(I))) == I + @test eval(Meta.parse(sprint(show, s[1]))) == s[1] + @test @constinferred(hash(s[1])) == hash(deepcopy(s[1])) + @test @constinferred(one(s[1])) == @constinferred(one(I)) + @constinferred dual(s[1]) + @constinferred dim(s[1]) + @constinferred frobeniusschur(s[1]) + @constinferred Nsymbol(s...) + @constinferred Rsymbol(s...) + @constinferred Bsymbol(s...) + @constinferred Fsymbol(s..., s...) + it = @constinferred s[1] ⊗ s[2] + @constinferred ⊗(s..., s...) + end + @testset "Sector $Istr: Value iterator" begin + @test eltype(values(I)) == I + sprev = one(I) + for (i, s) in enumerate(values(I)) + @test !isless(s, sprev) # confirm compatibility with sort order + if Base.IteratorSize(values(I)) == Base.IsInfinite() && I <: ProductSector + @test_throws ArgumentError values(I)[i] + @test_throws ArgumentError TensorKit.findindex(values(I), s) + elseif hasmethod(Base.getindex, Tuple{typeof(values(I)),Int}) + @test s == @constinferred (values(I)[i]) + @test TensorKit.findindex(values(I), s) == i + end + sprev = s + i >= 10 && break + end + @test one(I) == first(values(I)) if Base.IteratorSize(values(I)) == Base.IsInfinite() && I <: ProductSector - @test_throws ArgumentError values(I)[i] - @test_throws ArgumentError TensorKit.findindex(values(I), s) + @test_throws ArgumentError TensorKit.findindex(values(I), one(I)) elseif hasmethod(Base.getindex, Tuple{typeof(values(I)),Int}) - @test s == @constinferred (values(I)[i]) - @test TensorKit.findindex(values(I), s) == i - end - sprev = s - i >= 10 && break - end - @test one(I) == first(values(I)) - if Base.IteratorSize(values(I)) == Base.IsInfinite() && I <: ProductSector - @test_throws ArgumentError TensorKit.findindex(values(I), one(I)) - elseif hasmethod(Base.getindex, Tuple{typeof(values(I)),Int}) - @test (@constinferred TensorKit.findindex(values(I), one(I))) == 1 - for s in smallset(I) - @test (@constinferred values(I)[TensorKit.findindex(values(I), s)]) == s + @test (@constinferred TensorKit.findindex(values(I), one(I))) == 1 + for s in smallset(I) + @test (@constinferred values(I)[TensorKit.findindex(values(I), s)]) == s + end end end - end - if hasfusiontensor(I) - @testset "Sector $I: fusion tensor and F-move and R-move" begin - for a in smallset(I), b in smallset(I) - for c in ⊗(a, b) - X1 = permutedims(fusiontensor(a, b, c), (2, 1, 3, 4)) - X2 = fusiontensor(b, a, c) - l = dim(a) * dim(b) * dim(c) - R = LinearAlgebra.transpose(Rsymbol(a, b, c)) - sz = (l, convert(Int, Nsymbol(a, b, c))) - @test reshape(X1, sz) ≈ reshape(X2, sz) * R + if hasfusiontensor(I) + @testset "Sector $I: fusion tensor and F-move and R-move" begin + for a in smallset(I), b in smallset(I) + for c in ⊗(a, b) + X1 = permutedims(fusiontensor(a, b, c), (2, 1, 3, 4)) + X2 = fusiontensor(b, a, c) + l = dim(a) * dim(b) * dim(c) + R = LinearAlgebra.transpose(Rsymbol(a, b, c)) + sz = (l, convert(Int, Nsymbol(a, b, c))) + @test reshape(X1, sz) ≈ reshape(X2, sz) * R + end end - end - for a in smallset(I), b in smallset(I), c in smallset(I) - for e in ⊗(a, b), f in ⊗(b, c) - for d in intersect(⊗(e, c), ⊗(a, f)) - X1 = fusiontensor(a, b, e) - X2 = fusiontensor(e, c, d) - Y1 = fusiontensor(b, c, f) - Y2 = fusiontensor(a, f, d) - @tensor f1[-1, -2, -3, -4] := conj(Y2[a, f, d, -4]) * - conj(Y1[b, c, f, -3]) * - X1[a, b, e, -1] * X2[e, c, d, -2] - if FusionStyle(I) isa MultiplicityFreeFusion - f2 = fill(Fsymbol(a, b, c, d, e, f) * dim(d), (1, 1, 1, 1)) - else - f2 = Fsymbol(a, b, c, d, e, f) * dim(d) + for a in smallset(I), b in smallset(I), c in smallset(I) + for e in ⊗(a, b), f in ⊗(b, c) + for d in intersect(⊗(e, c), ⊗(a, f)) + X1 = fusiontensor(a, b, e) + X2 = fusiontensor(e, c, d) + Y1 = fusiontensor(b, c, f) + Y2 = fusiontensor(a, f, d) + @tensor f1[-1, -2, -3, -4] := conj(Y2[a, f, d, -4]) * + conj(Y1[b, c, f, -3]) * + X1[a, b, e, -1] * X2[e, c, d, -2] + if FusionStyle(I) isa MultiplicityFreeFusion + f2 = fill(Fsymbol(a, b, c, d, e, f) * dim(d), (1, 1, 1, 1)) + else + f2 = Fsymbol(a, b, c, d, e, f) * dim(d) + end + @test isapprox(f1, f2; atol=1e-12, rtol=1e-12) end - @test isapprox(f1, f2; atol=1e-12, rtol=1e-12) end end end end - end - @testset "Sector $Istr: Unitarity of F-move" begin - for a in smallset(I), b in smallset(I), c in smallset(I) - for d in ⊗(a, b, c) - es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) - fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) - if FusionStyle(I) isa MultiplicityFreeFusion - @test length(es) == length(fs) - F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] - else - Fblocks = Vector{Any}() - for e in es - for f in fs - Fs = Fsymbol(a, b, c, d, e, f) - push!(Fblocks, - reshape(Fs, - (size(Fs, 1) * size(Fs, 2), - size(Fs, 3) * size(Fs, 4)))) + @testset "Sector $Istr: Unitarity of F-move" begin + for a in smallset(I), b in smallset(I), c in smallset(I) + for d in ⊗(a, b, c) + es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d))))) + fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a)))) + if FusionStyle(I) isa MultiplicityFreeFusion + @test length(es) == length(fs) + F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs] + else + Fblocks = Vector{Any}() + for e in es + for f in fs + Fs = Fsymbol(a, b, c, d, e, f) + push!(Fblocks, + reshape(Fs, + (size(Fs, 1) * size(Fs, 2), + size(Fs, 3) * size(Fs, 4)))) + end end + F = hvcat(length(fs), Fblocks...) end - F = hvcat(length(fs), Fblocks...) + @test isapprox(F' * F, one(F); atol=1e-12, rtol=1e-12) end - @test isapprox(F' * F, one(F); atol=1e-12, rtol=1e-12) end end - end - @testset "Sector $Istr: Pentagon equation" begin - for a in smallset(I), b in smallset(I), c in smallset(I), d in smallset(I) - @test pentagon_equation(a, b, c, d; atol=1e-12, rtol=1e-12) + @testset "Sector $Istr: Pentagon equation" begin + for a in smallset(I), b in smallset(I), c in smallset(I), d in smallset(I) + @test pentagon_equation(a, b, c, d; atol=1e-12, rtol=1e-12) + end end - end - @testset "Sector $Istr: Hexagon equation" begin - for a in smallset(I), b in smallset(I), c in smallset(I) - @test hexagon_equation(a, b, c; atol=1e-12, rtol=1e-12) + @testset "Sector $Istr: Hexagon equation" begin + for a in smallset(I), b in smallset(I), c in smallset(I) + @test hexagon_equation(a, b, c; atol=1e-12, rtol=1e-12) + end end end -end -tf = time() -printstyled("Finished sector tests in ", - string(round(tf - ti; sigdigits=3)), - " seconds."; bold=true, color=Base.info_color()) -println() +end \ No newline at end of file diff --git a/test/spaces.jl b/test/spaces.jl index 6a23e903..ed676736 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -1,408 +1,405 @@ println("------------------------------------") println("| Fields and vector spaces |") println("------------------------------------") -ti = time() -@timedtestset "Fields" begin - @test isa(ℝ, Field) - @test isa(ℂ, Field) - @test eval(Meta.parse(sprint(show, ℝ))) == ℝ - @test eval(Meta.parse(sprint(show, ℂ))) == ℂ - @test ℝ ⊆ ℝ - @test ℝ ⊆ ℂ - @test ℂ ⊆ ℂ - @test !(ℂ ⊆ ℝ) +@timedtestset "Fields and vector spaces" verbose = true begin + @timedtestset "Fields" begin + @test isa(ℝ, Field) + @test isa(ℂ, Field) + @test eval(Meta.parse(sprint(show, ℝ))) == ℝ + @test eval(Meta.parse(sprint(show, ℂ))) == ℂ + @test ℝ ⊆ ℝ + @test ℝ ⊆ ℂ + @test ℂ ⊆ ℂ + @test !(ℂ ⊆ ℝ) - for T in (Int8, Int16, Int32, Int64, BigInt) - @test one(T) ∈ ℝ - @test one(Rational{T}) ∈ ℝ - @test !(one(Complex{T}) ∈ ℝ) - @test !(one(Complex{Rational{T}}) ∈ ℝ) - @test one(T) ∈ ℂ - @test one(Rational{T}) ∈ ℂ - @test one(Complex{T}) ∈ ℂ - @test one(Complex{Rational{T}} ∈ ℂ) + for T in (Int8, Int16, Int32, Int64, BigInt) + @test one(T) ∈ ℝ + @test one(Rational{T}) ∈ ℝ + @test !(one(Complex{T}) ∈ ℝ) + @test !(one(Complex{Rational{T}}) ∈ ℝ) + @test one(T) ∈ ℂ + @test one(Rational{T}) ∈ ℂ + @test one(Complex{T}) ∈ ℂ + @test one(Complex{Rational{T}} ∈ ℂ) - @test T ⊆ ℝ - @test Rational{T} ⊆ ℝ - @test !(Complex{T} ⊆ ℝ) - @test !(Complex{Rational{T}} ⊆ ℝ) - @test T ⊆ ℂ - @test Rational{T} ⊆ ℂ - @test Complex{T} ⊆ ℂ - @test Complex{Rational{T}} ⊆ ℂ - end - for T in (Float32, Float64, BigFloat) - @test one(T) ∈ ℝ - @test !(one(Complex{T}) ∈ ℝ) - @test one(T) ∈ ℂ - @test one(Complex{T} ∈ ℂ) + @test T ⊆ ℝ + @test Rational{T} ⊆ ℝ + @test !(Complex{T} ⊆ ℝ) + @test !(Complex{Rational{T}} ⊆ ℝ) + @test T ⊆ ℂ + @test Rational{T} ⊆ ℂ + @test Complex{T} ⊆ ℂ + @test Complex{Rational{T}} ⊆ ℂ + end + for T in (Float32, Float64, BigFloat) + @test one(T) ∈ ℝ + @test !(one(Complex{T}) ∈ ℝ) + @test one(T) ∈ ℂ + @test one(Complex{T} ∈ ℂ) - @test T ⊆ ℝ - @test !(Complex{T} ⊆ ℝ) - @test T ⊆ ℂ - @test Complex{T} ⊆ ℂ + @test T ⊆ ℝ + @test !(Complex{T} ⊆ ℝ) + @test T ⊆ ℂ + @test Complex{T} ⊆ ℂ + end end -end - -@timedtestset "ElementarySpace: CartesianSpace" begin - d = 2 - V = ℝ^d - @test eval(Meta.parse(sprint(show, V))) == V - @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) - @test isa(V, VectorSpace) - @test isa(V, ElementarySpace) - @test isa(InnerProductStyle(V), HasInnerProduct) - @test isa(InnerProductStyle(V), EuclideanProduct) - @test isa(V, CartesianSpace) - @test !isdual(V) - @test !isdual(V') - @test V == CartesianSpace(Trivial() => d) == CartesianSpace(Dict(Trivial() => d)) - @test @constinferred(hash(V)) == hash(deepcopy(V)) - @test V == @constinferred(dual(V)) == @constinferred(conj(V)) == - @constinferred(adjoint(V)) - @test field(V) == ℝ - @test @constinferred(sectortype(V)) == Trivial - @test ((@constinferred sectors(V))...,) == (Trivial(),) - @test length(sectors(V)) == 1 - @test @constinferred(TensorKit.hassector(V, Trivial())) - @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test dim(@constinferred(typeof(V)())) == 0 - @test (sectors(typeof(V)())...,) == () - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) - @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) - W = @constinferred ℝ^1 - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) - @test @constinferred(⊕(V, V)) == ℝ^(2d) - @test @constinferred(⊕(V, oneunit(V))) == ℝ^(d + 1) - @test @constinferred(⊕(V, V, V, V)) == ℝ^(4d) - @test @constinferred(fuse(V, V)) == ℝ^(d^2) - @test @constinferred(fuse(V, V', V, V')) == ℝ^(d^4) - @test @constinferred(flip(V)) == V' - @test flip(V) ≅ V - @test flip(V) ≾ V - @test flip(V) ≿ V - @test V ≺ ⊕(V, V) - @test !(V ≻ ⊕(V, V)) - @test @constinferred(infimum(V, ℝ^3)) == V - @test @constinferred(supremum(V', ℝ^3)) == ℝ^3 -end - -@timedtestset "ElementarySpace: ComplexSpace" begin - d = 2 - V = ℂ^d - @test eval(Meta.parse(sprint(show, V))) == V - @test eval(Meta.parse(sprint(show, V'))) == V' - @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) - @test isa(V, VectorSpace) - @test isa(V, ElementarySpace) - @test isa(InnerProductStyle(V), HasInnerProduct) - @test isa(InnerProductStyle(V), EuclideanProduct) - @test isa(V, ComplexSpace) - @test !isdual(V) - @test isdual(V') - @test V == ComplexSpace(Trivial() => d) == ComplexSpace(Dict(Trivial() => d)) - @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') - @test @constinferred(dual(V)) == @constinferred(conj(V)) == - @constinferred(adjoint(V)) != V - @test @constinferred(field(V)) == ℂ - @test @constinferred(sectortype(V)) == Trivial - @test @constinferred(sectortype(V)) == Trivial - @test ((@constinferred sectors(V))...,) == (Trivial(),) - @test length(sectors(V)) == 1 - @test @constinferred(TensorKit.hassector(V, Trivial())) - @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test dim(@constinferred(typeof(V)())) == 0 - @test (sectors(typeof(V)())...,) == () - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) - @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) - W = @constinferred ℂ^1 - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) - @test @constinferred(⊕(V, V)) == ℂ^(2d) - @test_throws SpaceMismatch (⊕(V, V')) - @test_throws MethodError (⊕(ℝ^d, ℂ^d)) - @test_throws MethodError (⊗(ℝ^d, ℂ^d)) - @test @constinferred(⊕(V, V)) == ℂ^(2d) - @test @constinferred(⊕(V, oneunit(V))) == ℂ^(d + 1) - @test @constinferred(⊕(V, V, V, V)) == ℂ^(4d) - @test @constinferred(fuse(V, V)) == ℂ^(d^2) - @test @constinferred(fuse(V, V', V, V')) == ℂ^(d^4) - @test @constinferred(flip(V)) == V' - @test flip(V) ≅ V - @test flip(V) ≾ V - @test flip(V) ≿ V - @test V ≺ ⊕(V, V) - @test !(V ≻ ⊕(V, V)) - @test @constinferred(infimum(V, ℂ^3)) == V - @test @constinferred(supremum(V', (ℂ^3)')) == dual(ℂ^3) == conj(ℂ^3) -end -@timedtestset "ElementarySpace: GeneralSpace" begin - d = 2 - V = GeneralSpace{ℂ}(d) - @test eval(Meta.parse(sprint(show, V))) == V - @test eval(Meta.parse(sprint(show, dual(V)))) == dual(V) - @test eval(Meta.parse(sprint(show, conj(V)))) == conj(V) - @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) - @test !isdual(V) - @test isdual(V') - @test !isdual(conj(V)) - @test isdual(conj(V')) - @test !TensorKit.isconj(V) - @test !TensorKit.isconj(V') - @test TensorKit.isconj(conj(V)) - @test TensorKit.isconj(conj(V')) - @test isa(V, VectorSpace) - @test isa(V, ElementarySpace) - @test !isa(InnerProductStyle(V), HasInnerProduct) - @test !isa(InnerProductStyle(V), EuclideanProduct) - @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') - @test @constinferred(dual(V)) != @constinferred(conj(V)) != V - @test @constinferred(field(V)) == ℂ - @test @constinferred(sectortype(V)) == Trivial - @test @constinferred(TensorKit.hassector(V, Trivial())) - @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) -end - -@timedtestset "ElementarySpace: $(TensorKit.type_repr(Vect[I]))" for I in sectorlist - if Base.IteratorSize(values(I)) === Base.IsInfinite() - set = unique(vcat(one(I), [randsector(I) for k in 1:10])) - gen = (c => 2 for c in set) - else - gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) + @timedtestset "ElementarySpace: CartesianSpace" begin + d = 2 + V = ℝ^d + @test eval(Meta.parse(sprint(show, V))) == V + @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) + @test isa(V, VectorSpace) + @test isa(V, ElementarySpace) + @test isa(InnerProductStyle(V), HasInnerProduct) + @test isa(InnerProductStyle(V), EuclideanProduct) + @test isa(V, CartesianSpace) + @test !isdual(V) + @test !isdual(V') + @test V == CartesianSpace(Trivial() => d) == CartesianSpace(Dict(Trivial() => d)) + @test @constinferred(hash(V)) == hash(deepcopy(V)) + @test V == @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) + @test field(V) == ℝ + @test @constinferred(sectortype(V)) == Trivial + @test ((@constinferred sectors(V))...,) == (Trivial(),) + @test length(sectors(V)) == 1 + @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) + @test dim(@constinferred(typeof(V)())) == 0 + @test (sectors(typeof(V)())...,) == () + @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) + @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) + W = @constinferred ℝ^1 + @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) + @test @constinferred(⊕(V, V)) == ℝ^(2d) + @test @constinferred(⊕(V, oneunit(V))) == ℝ^(d + 1) + @test @constinferred(⊕(V, V, V, V)) == ℝ^(4d) + @test @constinferred(fuse(V, V)) == ℝ^(d^2) + @test @constinferred(fuse(V, V', V, V')) == ℝ^(d^4) + @test @constinferred(flip(V)) == V' + @test flip(V) ≅ V + @test flip(V) ≾ V + @test flip(V) ≿ V + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) + @test @constinferred(infimum(V, ℝ^3)) == V + @test @constinferred(supremum(V', ℝ^3)) == ℝ^3 end - V = GradedSpace(gen) - @test eval(Meta.parse(TensorKit.type_repr(typeof(V)))) == typeof(V) - @test eval(Meta.parse(sprint(show, V))) == V - @test eval(Meta.parse(sprint(show, V'))) == V' - @test V' == GradedSpace(gen; dual=true) - @test V == @constinferred GradedSpace(gen...) - @test V' == @constinferred GradedSpace(gen...; dual=true) - @test V == @constinferred GradedSpace(tuple(gen...)) - @test V' == @constinferred GradedSpace(tuple(gen...); dual=true) - @test V == @constinferred GradedSpace(Dict(gen)) - @test V' == @constinferred GradedSpace(Dict(gen); dual=true) - @test V == @inferred Vect[I](gen) - @test V' == @constinferred Vect[I](gen; dual=true) - @test V == @constinferred Vect[I](gen...) - @test V' == @constinferred Vect[I](gen...; dual=true) - @test V == @constinferred Vect[I](Dict(gen)) - @test V' == @constinferred Vect[I](Dict(gen); dual=true) - @test V == @constinferred typeof(V)(c => dim(V, c) for c in sectors(V)) - if I isa ZNIrrep - @test V == @constinferred typeof(V)(V.dims) - @test V' == @constinferred typeof(V)(V.dims; dual=true) + + @timedtestset "ElementarySpace: ComplexSpace" begin + d = 2 + V = ℂ^d + @test eval(Meta.parse(sprint(show, V))) == V + @test eval(Meta.parse(sprint(show, V'))) == V' + @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) + @test isa(V, VectorSpace) + @test isa(V, ElementarySpace) + @test isa(InnerProductStyle(V), HasInnerProduct) + @test isa(InnerProductStyle(V), EuclideanProduct) + @test isa(V, ComplexSpace) + @test !isdual(V) + @test isdual(V') + @test V == ComplexSpace(Trivial() => d) == ComplexSpace(Dict(Trivial() => d)) + @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') + @test @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) != V + @test @constinferred(field(V)) == ℂ + @test @constinferred(sectortype(V)) == Trivial + @test @constinferred(sectortype(V)) == Trivial + @test ((@constinferred sectors(V))...,) == (Trivial(),) + @test length(sectors(V)) == 1 + @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) + @test dim(@constinferred(typeof(V)())) == 0 + @test (sectors(typeof(V)())...,) == () + @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) + @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) + W = @constinferred ℂ^1 + @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) + @test @constinferred(⊕(V, V)) == ℂ^(2d) + @test_throws SpaceMismatch (⊕(V, V')) + @test_throws MethodError (⊕(ℝ^d, ℂ^d)) + @test_throws MethodError (⊗(ℝ^d, ℂ^d)) + @test @constinferred(⊕(V, V)) == ℂ^(2d) + @test @constinferred(⊕(V, oneunit(V))) == ℂ^(d + 1) + @test @constinferred(⊕(V, V, V, V)) == ℂ^(4d) + @test @constinferred(fuse(V, V)) == ℂ^(d^2) + @test @constinferred(fuse(V, V', V, V')) == ℂ^(d^4) + @test @constinferred(flip(V)) == V' + @test flip(V) ≅ V + @test flip(V) ≾ V + @test flip(V) ≿ V + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) + @test @constinferred(infimum(V, ℂ^3)) == V + @test @constinferred(supremum(V', (ℂ^3)')) == dual(ℂ^3) == conj(ℂ^3) end - @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') - @test V == GradedSpace(reverse(collect(gen))...) - @test eval(Meta.parse(sprint(show, V))) == V - @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) - # space with no sectors - @test dim(@constinferred(typeof(V)())) == 0 - # space with a single sector - W = @constinferred GradedSpace(one(I) => 1) - @test W == GradedSpace(one(I) => 1, randsector(I) => 0) - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) - # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError GradedSpace(one(I) => 1, randsector(I) => 0, one(I) => 3) - @test eval(Meta.parse(sprint(show, W))) == W - @test isa(V, VectorSpace) - @test isa(V, ElementarySpace) - @test isa(InnerProductStyle(V), HasInnerProduct) - @test isa(InnerProductStyle(V), EuclideanProduct) - @test isa(V, GradedSpace) - @test isa(V, GradedSpace{I}) - @test @constinferred(dual(V)) == @constinferred(conj(V)) == - @constinferred(adjoint(V)) != V - @test @constinferred(field(V)) == ℂ - @test @constinferred(sectortype(V)) == I - slist = @constinferred sectors(V) - @test @constinferred(TensorKit.hassector(V, first(slist))) - @test @constinferred(dim(V)) == sum(dim(s) * dim(V, s) for s in slist) - @constinferred dim(V, first(slist)) - if hasfusiontensor(I) - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(dim(V)) + + @timedtestset "ElementarySpace: GeneralSpace" begin + d = 2 + V = GeneralSpace{ℂ}(d) + @test eval(Meta.parse(sprint(show, V))) == V + @test eval(Meta.parse(sprint(show, dual(V)))) == dual(V) + @test eval(Meta.parse(sprint(show, conj(V)))) == conj(V) + @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) + @test !isdual(V) + @test isdual(V') + @test !isdual(conj(V)) + @test isdual(conj(V')) + @test !TensorKit.isconj(V) + @test !TensorKit.isconj(V') + @test TensorKit.isconj(conj(V)) + @test TensorKit.isconj(conj(V')) + @test isa(V, VectorSpace) + @test isa(V, ElementarySpace) + @test !isa(InnerProductStyle(V), HasInnerProduct) + @test !isa(InnerProductStyle(V), EuclideanProduct) + @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') + @test @constinferred(dual(V)) != @constinferred(conj(V)) != V + @test @constinferred(field(V)) == ℂ + @test @constinferred(sectortype(V)) == Trivial + @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) + @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) end - @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) - @test @constinferred(⊕(V, V, V, V)) == Vect[I](c => 4dim(V, c) for c in sectors(V)) - @test @constinferred(⊕(V, oneunit(V))) == - Vect[I](c => isone(c) + dim(V, c) for c in sectors(V)) - @test @constinferred(fuse(V, oneunit(V))) == V - d = Dict{I,Int}() - for a in sectors(V), b in sectors(V) - for c in a ⊗ b - d[c] = get(d, c, 0) + dim(V, a) * dim(V, b) * Nsymbol(a, b, c) + + @timedtestset "ElementarySpace: $(TensorKit.type_repr(Vect[I]))" for I in sectorlist + if Base.IteratorSize(values(I)) === Base.IsInfinite() + set = unique(vcat(one(I), [randsector(I) for k in 1:10])) + gen = (c => 2 for c in set) + else + gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) end + V = GradedSpace(gen) + @test eval(Meta.parse(TensorKit.type_repr(typeof(V)))) == typeof(V) + @test eval(Meta.parse(sprint(show, V))) == V + @test eval(Meta.parse(sprint(show, V'))) == V' + @test V' == GradedSpace(gen; dual=true) + @test V == @constinferred GradedSpace(gen...) + @test V' == @constinferred GradedSpace(gen...; dual=true) + @test V == @constinferred GradedSpace(tuple(gen...)) + @test V' == @constinferred GradedSpace(tuple(gen...); dual=true) + @test V == @constinferred GradedSpace(Dict(gen)) + @test V' == @constinferred GradedSpace(Dict(gen); dual=true) + @test V == @inferred Vect[I](gen) + @test V' == @constinferred Vect[I](gen; dual=true) + @test V == @constinferred Vect[I](gen...) + @test V' == @constinferred Vect[I](gen...; dual=true) + @test V == @constinferred Vect[I](Dict(gen)) + @test V' == @constinferred Vect[I](Dict(gen); dual=true) + @test V == @constinferred typeof(V)(c => dim(V, c) for c in sectors(V)) + if I isa ZNIrrep + @test V == @constinferred typeof(V)(V.dims) + @test V' == @constinferred typeof(V)(V.dims; dual=true) + end + @test @constinferred(hash(V)) == hash(deepcopy(V)) != hash(V') + @test V == GradedSpace(reverse(collect(gen))...) + @test eval(Meta.parse(sprint(show, V))) == V + @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) + # space with no sectors + @test dim(@constinferred(typeof(V)())) == 0 + # space with a single sector + W = @constinferred GradedSpace(one(I) => 1) + @test W == GradedSpace(one(I) => 1, randsector(I) => 0) + @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) + # randsector never returns trivial sector, so this cannot error + @test_throws ArgumentError GradedSpace(one(I) => 1, randsector(I) => 0, one(I) => 3) + @test eval(Meta.parse(sprint(show, W))) == W + @test isa(V, VectorSpace) + @test isa(V, ElementarySpace) + @test isa(InnerProductStyle(V), HasInnerProduct) + @test isa(InnerProductStyle(V), EuclideanProduct) + @test isa(V, GradedSpace) + @test isa(V, GradedSpace{I}) + @test @constinferred(dual(V)) == @constinferred(conj(V)) == + @constinferred(adjoint(V)) != V + @test @constinferred(field(V)) == ℂ + @test @constinferred(sectortype(V)) == I + slist = @constinferred sectors(V) + @test @constinferred(TensorKit.hassector(V, first(slist))) + @test @constinferred(dim(V)) == sum(dim(s) * dim(V, s) for s in slist) + @constinferred dim(V, first(slist)) + if hasfusiontensor(I) + @test @constinferred(TensorKit.axes(V)) == Base.OneTo(dim(V)) + end + @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) + @test @constinferred(⊕(V, V, V, V)) == Vect[I](c => 4dim(V, c) for c in sectors(V)) + @test @constinferred(⊕(V, oneunit(V))) == + Vect[I](c => isone(c) + dim(V, c) for c in sectors(V)) + @test @constinferred(fuse(V, oneunit(V))) == V + d = Dict{I,Int}() + for a in sectors(V), b in sectors(V) + for c in a ⊗ b + d[c] = get(d, c, 0) + dim(V, a) * dim(V, b) * Nsymbol(a, b, c) + end + end + @test @constinferred(fuse(V, V)) == GradedSpace(d) + @test @constinferred(flip(V)) == + Vect[I](conj(c) => dim(V, c) for c in sectors(V))' + @test flip(V) ≅ V + @test flip(V) ≾ V + @test flip(V) ≿ V + @test @constinferred(⊕(V, V)) == @constinferred supremum(V, ⊕(V, V)) + @test V == @constinferred infimum(V, ⊕(V, V)) + @test V ≺ ⊕(V, V) + @test !(V ≻ ⊕(V, V)) + @test infimum(V, GradedSpace(one(I) => 3)) == GradedSpace(one(I) => 2) + @test_throws SpaceMismatch (⊕(V, V')) end - @test @constinferred(fuse(V, V)) == GradedSpace(d) - @test @constinferred(flip(V)) == - Vect[I](conj(c) => dim(V, c) for c in sectors(V))' - @test flip(V) ≅ V - @test flip(V) ≾ V - @test flip(V) ≿ V - @test @constinferred(⊕(V, V)) == @constinferred supremum(V, ⊕(V, V)) - @test V == @constinferred infimum(V, ⊕(V, V)) - @test V ≺ ⊕(V, V) - @test !(V ≻ ⊕(V, V)) - @test infimum(V, GradedSpace(one(I) => 3)) == GradedSpace(one(I) => 2) - @test_throws SpaceMismatch (⊕(V, V')) -end -@timedtestset "ProductSpace{ℂ}" begin - V1, V2, V3, V4 = ℂ^1, ℂ^2, ℂ^3, ℂ^4 - P = @constinferred ProductSpace(V1, V2, V3, V4) - @test eval(Meta.parse(sprint(show, P))) == P - @test eval(Meta.parse(sprint(show, typeof(P)))) == typeof(P) - @test isa(P, VectorSpace) - @test isa(P, CompositeSpace) - @test spacetype(P) == ComplexSpace - @test sectortype(P) == Trivial - @test @constinferred(hash(P)) == hash(deepcopy(P)) != hash(P') - @test P == deepcopy(P) - @test P == typeof(P)(P...) - @constinferred (x -> tuple(x...))(P) - @test @constinferred(dual(P)) == P' - @test @constinferred(field(P)) == ℂ - @test @constinferred(*(V1, V2, V3, V4)) == P - @test @constinferred(⊗(V1, V2, V3, V4)) == P - @test @constinferred(⊗(V1, V2 ⊗ V3 ⊗ V4)) == P - @test @constinferred(⊗(V1 ⊗ V2, V3 ⊗ V4)) == P - @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P - @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P - @test @constinferred(insertunit(P, 3)) == V1 * V2 * oneunit(V1) * V3 * V4 - @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 - @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 - @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 - @test fuse(flip(V1), V2, flip(V3)) ≅ V1 ⊗ V2 ⊗ V3 - @test @constinferred(⊗(P)) == P - @test @constinferred(⊗(V1)) == ProductSpace(V1) - @test eval(Meta.parse(sprint(show, ⊗(V1)))) == ⊗(V1) - @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == - @constinferred(one(P)) == @constinferred(one(typeof(P))) == - ProductSpace{ComplexSpace}(()) - @test eval(Meta.parse(sprint(show, one(P)))) == one(P) - @test @constinferred(⊗(one(P), P)) == P - @test @constinferred(⊗(P, one(P))) == P - @test @constinferred(⊗(one(P), one(P))) == one(P) - @test @constinferred(adjoint(P)) == dual(P) == V4' ⊗ V3' ⊗ V2' ⊗ V1' - @test @constinferred(dims(P)) == map(dim, (V1, V2, V3, V4)) - @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3, V4)) - @test @constinferred(dim(P, 2)) == dim(V2) - @test first(@constinferred(sectors(P))) == (Trivial(), Trivial(), Trivial(), Trivial()) - cube(x) = x^3 - @test @constinferred(cube(V1)) == V1 ⊗ V1 ⊗ V1 - N = 3 - @test V1^N == V1 ⊗ V1 ⊗ V1 - @test P^2 == P ⊗ P - @test @constinferred(dims(P, first(sectors(P)))) == dims(P) - @test ((@constinferred blocksectors(P))...,) == (Trivial(),) - @test isempty(blocksectors(P ⊗ ℂ^0)) - @test isempty(@constinferred(sectors(P ⊗ ℂ^0))) - @test @constinferred(blockdim(P, first(blocksectors(P)))) == dim(P) - @test Base.IteratorEltype(P) == Base.IteratorEltype(typeof(P)) == - Base.IteratorEltype(P.spaces) - @test Base.IteratorSize(P) == Base.IteratorSize(typeof(P)) == - Base.IteratorSize(P.spaces) - @test Base.eltype(P) == Base.eltype(typeof(P)) == typeof(V1) - @test eltype(collect(P)) == typeof(V1) - @test collect(P) == [V1, V2, V3, V4] -end + @timedtestset "ProductSpace{ℂ}" begin + V1, V2, V3, V4 = ℂ^1, ℂ^2, ℂ^3, ℂ^4 + P = @constinferred ProductSpace(V1, V2, V3, V4) + @test eval(Meta.parse(sprint(show, P))) == P + @test eval(Meta.parse(sprint(show, typeof(P)))) == typeof(P) + @test isa(P, VectorSpace) + @test isa(P, CompositeSpace) + @test spacetype(P) == ComplexSpace + @test sectortype(P) == Trivial + @test @constinferred(hash(P)) == hash(deepcopy(P)) != hash(P') + @test P == deepcopy(P) + @test P == typeof(P)(P...) + @constinferred (x -> tuple(x...))(P) + @test @constinferred(dual(P)) == P' + @test @constinferred(field(P)) == ℂ + @test @constinferred(*(V1, V2, V3, V4)) == P + @test @constinferred(⊗(V1, V2, V3, V4)) == P + @test @constinferred(⊗(V1, V2 ⊗ V3 ⊗ V4)) == P + @test @constinferred(⊗(V1 ⊗ V2, V3 ⊗ V4)) == P + @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P + @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P + @test @constinferred(insertunit(P, 3)) == V1 * V2 * oneunit(V1) * V3 * V4 + @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 + @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 + @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 + @test fuse(flip(V1), V2, flip(V3)) ≅ V1 ⊗ V2 ⊗ V3 + @test @constinferred(⊗(P)) == P + @test @constinferred(⊗(V1)) == ProductSpace(V1) + @test eval(Meta.parse(sprint(show, ⊗(V1)))) == ⊗(V1) + @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == + @constinferred(one(P)) == @constinferred(one(typeof(P))) == + ProductSpace{ComplexSpace}(()) + @test eval(Meta.parse(sprint(show, one(P)))) == one(P) + @test @constinferred(⊗(one(P), P)) == P + @test @constinferred(⊗(P, one(P))) == P + @test @constinferred(⊗(one(P), one(P))) == one(P) + @test @constinferred(adjoint(P)) == dual(P) == V4' ⊗ V3' ⊗ V2' ⊗ V1' + @test @constinferred(dims(P)) == map(dim, (V1, V2, V3, V4)) + @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3, V4)) + @test @constinferred(dim(P, 2)) == dim(V2) + @test first(@constinferred(sectors(P))) == + (Trivial(), Trivial(), Trivial(), Trivial()) + cube(x) = x^3 + @test @constinferred(cube(V1)) == V1 ⊗ V1 ⊗ V1 + N = 3 + @test V1^N == V1 ⊗ V1 ⊗ V1 + @test P^2 == P ⊗ P + @test @constinferred(dims(P, first(sectors(P)))) == dims(P) + @test ((@constinferred blocksectors(P))...,) == (Trivial(),) + @test isempty(blocksectors(P ⊗ ℂ^0)) + @test isempty(@constinferred(sectors(P ⊗ ℂ^0))) + @test @constinferred(blockdim(P, first(blocksectors(P)))) == dim(P) + @test Base.IteratorEltype(P) == Base.IteratorEltype(typeof(P)) == + Base.IteratorEltype(P.spaces) + @test Base.IteratorSize(P) == Base.IteratorSize(typeof(P)) == + Base.IteratorSize(P.spaces) + @test Base.eltype(P) == Base.eltype(typeof(P)) == typeof(V1) + @test eltype(collect(P)) == typeof(V1) + @test collect(P) == [V1, V2, V3, V4] + end -@timedtestset "ProductSpace{SU₂Space}" begin - V1, V2, V3 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), - SU₂Space(1 // 2 => 1, 1 => 1)' - P = @constinferred ProductSpace(V1, V2, V3) - @test eval(Meta.parse(sprint(show, P))) == P - @test eval(Meta.parse(sprint(show, typeof(P)))) == typeof(P) - @test isa(P, VectorSpace) - @test isa(P, CompositeSpace) - @test spacetype(P) == SU₂Space - @test sectortype(P) == Irrep[SU₂] == SU2Irrep - @test @constinferred(hash(P)) == hash(deepcopy(P)) != hash(P') - @test @constinferred(dual(P)) == P' - @test @constinferred(field(P)) == ℂ - @test @constinferred(*(V1, V2, V3)) == P - @test @constinferred(⊗(V1, V2, V3)) == P - @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' - @test @constinferred(insertunit(P, 3; conj=true)) == V1 * V2 * oneunit(V1)' * V3 - @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 - @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) - @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 - @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 ≿ fuse(V1 ⊗ V2' ⊗ V3) - @test V1 ⊗ fuse(V2', V3) ≿ V1 ⊗ V2' ⊗ V3 - @test fuse(flip(V1) ⊗ V2) ⊗ flip(V3) ≅ V1 ⊗ V2 ⊗ V3 - @test @constinferred(⊗(V1)) == ProductSpace(V1) - @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == - @constinferred(one(P)) == @constinferred(one(typeof(P))) == - ProductSpace{ComplexSpace}(()) - @test @constinferred(dims(P)) == map(dim, (V1, V2, V3)) - @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3)) - for s in @constinferred(sectors(P)) - @test hassector(P, s) - @test @constinferred(dims(P, s)) == dim.((V1, V2, V3), s) + @timedtestset "ProductSpace{SU₂Space}" begin + V1, V2, V3 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), + SU₂Space(1 // 2 => 1, 1 => 1)' + P = @constinferred ProductSpace(V1, V2, V3) + @test eval(Meta.parse(sprint(show, P))) == P + @test eval(Meta.parse(sprint(show, typeof(P)))) == typeof(P) + @test isa(P, VectorSpace) + @test isa(P, CompositeSpace) + @test spacetype(P) == SU₂Space + @test sectortype(P) == Irrep[SU₂] == SU2Irrep + @test @constinferred(hash(P)) == hash(deepcopy(P)) != hash(P') + @test @constinferred(dual(P)) == P' + @test @constinferred(field(P)) == ℂ + @test @constinferred(*(V1, V2, V3)) == P + @test @constinferred(⊗(V1, V2, V3)) == P + @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' + @test @constinferred(insertunit(P, 3; conj=true)) == V1 * V2 * oneunit(V1)' * V3 + @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 + @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) + @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 + @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 ≿ fuse(V1 ⊗ V2' ⊗ V3) + @test V1 ⊗ fuse(V2', V3) ≿ V1 ⊗ V2' ⊗ V3 + @test fuse(flip(V1) ⊗ V2) ⊗ flip(V3) ≅ V1 ⊗ V2 ⊗ V3 + @test @constinferred(⊗(V1)) == ProductSpace(V1) + @test @constinferred(one(V1)) == @constinferred(one(typeof(V1))) == + @constinferred(one(P)) == @constinferred(one(typeof(P))) == + ProductSpace{ComplexSpace}(()) + @test @constinferred(dims(P)) == map(dim, (V1, V2, V3)) + @test @constinferred(dim(P)) == prod(dim, (V1, V2, V3)) + for s in @constinferred(sectors(P)) + @test hassector(P, s) + @test @constinferred(dims(P, s)) == dim.((V1, V2, V3), s) + end + @test sum(dim(c) * blockdim(P, c) for c in @constinferred(blocksectors(P))) == + dim(P) end - @test sum(dim(c) * blockdim(P, c) for c in @constinferred(blocksectors(P))) == dim(P) -end -@timedtestset "Deligne tensor product of spaces" begin - V1 = SU₂Space(0 => 3, 1 // 2 => 1) - V2 = SU₂Space(0 => 2, 1 => 1)' - V3 = ℤ₃Space(0 => 3, 1 => 2, 2 => 1) - V4 = ℂ^3 + @timedtestset "Deligne tensor product of spaces" begin + V1 = SU₂Space(0 => 3, 1 // 2 => 1) + V2 = SU₂Space(0 => 2, 1 => 1)' + V3 = ℤ₃Space(0 => 3, 1 => 2, 2 => 1) + V4 = ℂ^3 - for W1 in (V1, V2, V3, V4) - for W2 in (V1, V2, V3, V4) - for W3 in (V1, V2, V3, V4) - for W4 in (V1, V2, V3, V4) - Ws = @constinferred(W1 ⊠ W2 ⊠ W3 ⊠ W4) - @test Ws == @constinferred((W1 ⊠ W2) ⊠ (W3 ⊠ W4)) == - @constinferred(((W1 ⊠ W2) ⊠ W3) ⊠ W4) == - @constinferred((W1 ⊠ (W2 ⊠ W3)) ⊠ W4) == - @constinferred(W1 ⊠ ((W2 ⊠ W3)) ⊠ W4) == - @constinferred(W1 ⊠ (W2 ⊠ (W3 ⊠ W4))) - I1, I2, I3, I4 = map(sectortype, (W1, W2, W3, W4)) - I = sectortype(Ws) - @test I == @constinferred((I1 ⊠ I2) ⊠ (I3 ⊠ I4)) == - @constinferred(((I1 ⊠ I2) ⊠ I3) ⊠ I4) == - @constinferred((I1 ⊠ (I2 ⊠ I3)) ⊠ I4) == - @constinferred(I1 ⊠ ((I2 ⊠ I3)) ⊠ I4) == - @constinferred(I1 ⊠ (I2 ⊠ (I3 ⊠ I4))) - @test dim(Ws) == dim(W1) * dim(W2) * dim(W3) * dim(W4) + for W1 in (V1, V2, V3, V4) + for W2 in (V1, V2, V3, V4) + for W3 in (V1, V2, V3, V4) + for W4 in (V1, V2, V3, V4) + Ws = @constinferred(W1 ⊠ W2 ⊠ W3 ⊠ W4) + @test Ws == @constinferred((W1 ⊠ W2) ⊠ (W3 ⊠ W4)) == + @constinferred(((W1 ⊠ W2) ⊠ W3) ⊠ W4) == + @constinferred((W1 ⊠ (W2 ⊠ W3)) ⊠ W4) == + @constinferred(W1 ⊠ ((W2 ⊠ W3)) ⊠ W4) == + @constinferred(W1 ⊠ (W2 ⊠ (W3 ⊠ W4))) + I1, I2, I3, I4 = map(sectortype, (W1, W2, W3, W4)) + I = sectortype(Ws) + @test I == @constinferred((I1 ⊠ I2) ⊠ (I3 ⊠ I4)) == + @constinferred(((I1 ⊠ I2) ⊠ I3) ⊠ I4) == + @constinferred((I1 ⊠ (I2 ⊠ I3)) ⊠ I4) == + @constinferred(I1 ⊠ ((I2 ⊠ I3)) ⊠ I4) == + @constinferred(I1 ⊠ (I2 ⊠ (I3 ⊠ I4))) + @test dim(Ws) == dim(W1) * dim(W2) * dim(W3) * dim(W4) + end end end end + @test sectortype(@constinferred((V1 ⊗ V2) ⊠ V3)) == @constinferred(Irrep[SU₂ × ℤ₃]) + @test dim((V1 ⊗ V2) ⊠ V3) == dim(V1) * dim(V2) * dim(V3) + @test sectortype((V1 ⊗ V2) ⊠ V3 ⊠ V4) == Irrep[SU₂ × ℤ₃] + @test dim((V1 ⊗ V2) ⊠ V3 ⊠ V4) == dim(V1) * dim(V2) * dim(V3) * dim(V4) + @test fuse(V2 ⊠ V4) == fuse(V4 ⊠ V2) == SU₂Space(0 => 6, 1 => 3) + @test fuse(V3 ⊠ V4) == fuse(V4 ⊠ V3) == ℤ₃Space(0 => 9, 1 => 6, 2 => 3) end - @test sectortype(@constinferred((V1 ⊗ V2) ⊠ V3)) == @constinferred(Irrep[SU₂ × ℤ₃]) - @test dim((V1 ⊗ V2) ⊠ V3) == dim(V1) * dim(V2) * dim(V3) - @test sectortype((V1 ⊗ V2) ⊠ V3 ⊠ V4) == Irrep[SU₂ × ℤ₃] - @test dim((V1 ⊗ V2) ⊠ V3 ⊠ V4) == dim(V1) * dim(V2) * dim(V3) * dim(V4) - @test fuse(V2 ⊠ V4) == fuse(V4 ⊠ V2) == SU₂Space(0 => 6, 1 => 3) - @test fuse(V3 ⊠ V4) == fuse(V4 ⊠ V3) == ℤ₃Space(0 => 9, 1 => 6, 2 => 3) -end -@timedtestset "HomSpace" begin - V1, V2, V3, V4, V5 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), - SU₂Space(1 // 2 => 1, 1 => 1)', SU₂Space(0 => 2, 1 // 2 => 2), - SU₂Space(0 => 1, 1 // 2 => 1, 3 // 2 => 1)' - W = TensorKit.HomSpace(V1 ⊗ V2, V3 ⊗ V4 ⊗ V5) - @test W == (V3 ⊗ V4 ⊗ V5 → V1 ⊗ V2) - @test W == (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) - @test W' == (V1 ⊗ V2 → V3 ⊗ V4 ⊗ V5) - @test eval(Meta.parse(sprint(show, W))) == W - @test eval(Meta.parse(sprint(show, typeof(W)))) == typeof(W) - @test spacetype(W) == SU₂Space - @test sectortype(W) == Irrep[SU₂] - @test W[1] == V1 - @test W[2] == V2 - @test W[3] == V3' - @test W[4] == V4' - @test W[5] == V5' - @test @constinferred(hash(W)) == hash(deepcopy(W)) != hash(W') - @test W == deepcopy(W) -end - -tf = time() -printstyled("Finished vector space tests in ", - string(round(tf - ti; sigdigits=3)), - " seconds."; bold=true, color=Base.info_color()) -println() + @timedtestset "HomSpace" begin + V1, V2, V3, V4, V5 = SU₂Space(0 => 3, 1 // 2 => 1), SU₂Space(0 => 2, 1 => 1), + SU₂Space(1 // 2 => 1, 1 => 1)', SU₂Space(0 => 2, 1 // 2 => 2), + SU₂Space(0 => 1, 1 // 2 => 1, 3 // 2 => 1)' + W = TensorKit.HomSpace(V1 ⊗ V2, V3 ⊗ V4 ⊗ V5) + @test W == (V3 ⊗ V4 ⊗ V5 → V1 ⊗ V2) + @test W == (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) + @test W' == (V1 ⊗ V2 → V3 ⊗ V4 ⊗ V5) + @test eval(Meta.parse(sprint(show, W))) == W + @test eval(Meta.parse(sprint(show, typeof(W)))) == typeof(W) + @test spacetype(W) == SU₂Space + @test sectortype(W) == Irrep[SU₂] + @test W[1] == V1 + @test W[2] == V2 + @test W[3] == V3' + @test W[4] == V4' + @test W[5] == V5' + @test @constinferred(hash(W)) == hash(deepcopy(W)) != hash(W') + @test W == deepcopy(W) + end +end \ No newline at end of file diff --git a/test/tensors.jl b/test/tensors.jl index a7505bfe..09927bdc 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -78,7 +78,7 @@ for V in spacelist println("---------------------------------------") println("Tensors with symmetry: $Istr") println("---------------------------------------") - global ti = time() + @timedtestset "Tensors with symmetry: $Istr" verbose=true begin V1, V2, V3, V4, V5 = V @timedtestset "Basic tensor properties" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 @@ -579,11 +579,7 @@ for V in spacelist @test t ≈ t′ end end - global tf = time() - printstyled("Finished tensor tests with symmetry $Istr in ", - string(round(tf - ti; sigdigits=3)), - " seconds."; bold=true, color=Base.info_color()) - println() +end end @timedtestset "Deligne tensor product: test via conversion" begin From 8ae2a651c6127cca0e8e539eea09e88b9f524340 Mon Sep 17 00:00:00 2001 From: Jutho Date: Fri, 28 Jul 2023 01:38:20 +0200 Subject: [PATCH 26/57] restructure tensoroperations --- src/TensorKit.jl | 4 +- src/tensors/linalg.jl | 3 +- src/tensors/tensoroperations.jl | 326 ++++++++++++++------------------ 3 files changed, 146 insertions(+), 187 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 18a33dd6..81fc8882 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -92,7 +92,9 @@ using TupleTools: StaticLength using Strided using VectorInterface -using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon, IndexTuple, Index2Tuple, linearize + +using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon +using TensorOperations: IndexTuple, Index2Tuple, linearize, Backend const TO = TensorOperations using LRUCache diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index fd8b5c90..8b6c9410 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -455,8 +455,7 @@ function ⊗(t1::AbstractTensorMap{S}, t2::AbstractTensorMap{S}) where S end # deligne product of tensors -function ⊠(t1::AbstractTensorMap{<:ElementarySpace{ℂ}}, - t2::AbstractTensorMap{<:ElementarySpace{ℂ}}) +function ⊠(t1::AbstractTensorMap, t2::AbstractTensorMap) S1 = spacetype(t1) I1 = sectortype(S1) S2 = spacetype(t2) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 4cbf1971..fea99056 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -1,3 +1,143 @@ +# Implement full TensorOperations.jl interface +#---------------------------------------------- +TO.tensorstructure(t::AbstractTensorMap) = space(t) +function TO.tensorstructure(t::AbstractTensorMap, iA::Int, conjA::Symbol) + return conjA == :N ? space(t, iA) : conj(space(t, iA)) +end + +function TO.tensoralloc(ttype::Type{<:AbstractTensorMap}, structure, istemp=false, + backend::Backend...) + M = storagetype(ttype) + return TensorMap(structure) do d + return TO.tensoralloc(M, d, istemp, backend...) + end +end + +function TO.tensorfree!(t::AbstractTensorMap, backend::Backend...) + for (c, b) in blocks(t) + TO.tensorfree!(b, backend...) + end + return nothing +end + +TO.tensorscalar(t::AbstractTensorMap) = scalar(t) + +_canonicalize(p::Index2Tuple{N₁,N₂}, ::AbstractTensorMap{<:IndexSpace,N₁,N₂}) where {N₁,N₂} = p +function _canonicalize(p::Index2Tuple, ::AbstractTensorMap) + p′ = linearize(p) + p₁ = TupleTools.getindices(p′, codomainind(t)) + p₂ = TupleTools.getindices(p′, domainind(t)) + return (p₁, p₂) +end + +# tensoradd! +function TO.tensoradd!(C::AbstractTensorMap{S}, + A::AbstractTensorMap{S}, pC::Index2Tuple, conjA::Symbol, + α::Number, β::Number, backend::Backend...) where {S} + if conjA == :N + A′ = A + pC′ = _canonicalize(pC, C) + elseif conjA == :C + A′ = adjoint(A) + pC′ = adjointtensorindices(A, _canonicalize(pA, C)) + else + throw(ArgumentError("unknown conjugation flag $conjA")) + end + # TODO: novel syntax for tensoradd!? + # tensoradd!(C, A′, pC′, α, β, backend...) + add!(α, A′, β, C, pC′[1], pC′[2]) + return C +end + +function TO.tensoradd_type(TC, ::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, + ::Symbol) where {S,N₁,N₂} + M = similarstoragetype(A, TC) + return tensormaptype(S, N₁, N₂, M) +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 + else + return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pC), :N) + end +end + +# tensortrace! +function TO.tensortrace!(C::AbstractTensorMap{S}, pC::Index2Tuple, + A::AbstractTensorMap{S}, qA::Index2Tuple, conjA::Symbol, + α::Number, β::Number, backend::Backend...) where {S} + if conjA == :N + A′ = A + pC′ = _canonicalize(pC, C) + qA′ = qA + elseif conjA == :C + A′ = adjoint(A) + pC′ = adjointtensorindices(A, _canonicalize(pC, C)) + qA′ = adjointtensorindices(A, qA) + else + throw(ArgumentError("unknown conjugation flag $conjA")) + end + # TODO: novel syntax for tensortrace? + # tensortrace!(C, pC′, A′, qA′, α, β, backend...) + trace!(α, A′, β, C, pC′[1], pC′[2], qA′[1], qA′[2]) + return C +end + +# tensorcontract! +function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple, + A::AbstractTensorMap{S}, pA::Index2Tuple, conjA::Symbol, + B::AbstractTensorMap{S}, pB::Index2Tuple, conjB::Symbol, + α::Number, β::Number, backend::Backend...) where {S,N₁,N₂} + pC′ = _canonicalize(pC, C) + if conjA == :N + A′ = A + pA′ = pA + elseif conjA == :C + A′ = A' + pA′ = adjointtensorindices(A, pA) + else + throw(ArgumentError("unknown conjugation flag $conjA")) + end + if conjB == :N + B′ = B + pB′ = pB + elseif conjB == :C + B′ = B' + pB′ = adjointtensorindices(B, pB) + else + throw(ArgumentError("unknown conjugation flag $conjB")) + end + # TODO: novel syntax for tensorcontract? + # tensorcontract!(C, pC′, A′, pA′, B′, pB′, α, β, backend...) + contract!(α, A′, B′, β, C, pA′[1], pA′[2], pB′[2], pB′[1], pC′[1], pC′[2]) + return C +end + +function TO.tensorcontract_type(TC, ::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, pA, conjA, + B::AbstractTensorMap{S}, pB, conjB) where {S,N₁,N₂} + M = similarstoragetype(A, TC) + M == similarstoragetype(B, TC) || throw(ArgumentError("incompatible storage types")) + return tensormaptype(S, N₁, N₂, M) +end + +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 +end + +# Actual implementations function cached_permute(sym::Symbol, t::TensorMap{S}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=(); copy::Bool=false) where {S,N₁,N₂} @@ -202,7 +342,8 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N coeff *= twist(g1.uncoupled[i]) end end - TO.tensortrace!(tdst[f₁′′, f₂′′], (p1, p2), tsrc[f₁, f₂], (q1, q2), :N, α*coeff, true) + TO.tensortrace!(tdst[f₁′′, f₂′′], (p1, p2), tsrc[f₁, f₂], (q1, q2), :N, + α * coeff, true) end end end @@ -323,186 +464,3 @@ function scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} return dim(codomain(t)) == dim(domain(t)) == 1 ? first(blocks(t))[2][1, 1] : throw(DimensionMismatch()) end - -TO.tensorscalar(t::AbstractTensorMap) = scalar(t) - -function TO.tensoradd!(tdst::AbstractTensorMap{S}, - tsrc::AbstractTensorMap{S}, pA::Index2Tuple, - conjA::Symbol, α::Number, β::Number) where {S} - if conjA == :N - p = linearize(pA) - pl = TupleTools.getindices(p, codomainind(tdst)) - pr = TupleTools.getindices(p, domainind(tdst)) - add!(α, tsrc, β, tdst, pl, pr) - else - p = adjointtensorindices(tsrc, linearize(pA)) - pl = TupleTools.getindices(p, codomainind(tdst)) - pr = TupleTools.getindices(p, domainind(tdst)) - add!(α, adjoint(tsrc), β, tdst, pl, pr) - end - return tdst -end - -function TO.tensortrace!(tdst::AbstractTensorMap{S}, - pC::Index2Tuple, tsrc::AbstractTensorMap{S}, - pA::Index2Tuple, conjA::Symbol, α::Number, - β::Number) where {S} - if conjA == :N - p = linearize(pC) - pl = TupleTools.getindices(p, codomainind(tdst)) - pr = TupleTools.getindices(p, domainind(tdst)) - trace!(α, tsrc, β, tdst, pl, pr, pA[1], pA[2]) - else - p = adjointtensorindices(tsrc, linearize(pC)) - pl = TupleTools.getindices(p, codomainind(tdst)) - pr = TupleTools.getindices(p, domainind(tdst)) - q1 = adjointtensorindices(tsrc, pA[1]) - q2 = adjointtensorindices(tsrc, pA[2]) - trace!(α, adjoint(tsrc), β, tdst, pl, pr, q1, q2) - end - return tdst -end - -# # function TO.similarstructure_from_indices(T::Type, p1::IndexTuple, p2::IndexTuple, -# # A::AbstractTensorMap, CA::Symbol=:N) -# # if CA == :N -# # _similarstructure_from_indices(T, p1, p2, A) -# # else -# # p1 = adjointtensorindices(A, p1) -# # p2 = adjointtensorindices(A, p2) -# # _similarstructure_from_indices(T, p1, p2, adjoint(A)) -# # end -# # end - -# # function TO.similarstructure_from_indices(T::Type, poA::IndexTuple, poB::IndexTuple, -# # p1::IndexTuple, p2::IndexTuple, -# # A::AbstractTensorMap, B::AbstractTensorMap, -# # CA::Symbol=:N, CB::Symbol=:N) -# # if CA == :N && CB == :N -# # _similarstructure_from_indices(T, poA, poB, p1, p2, A, B) -# # elseif CA == :C && CB == :N -# # poA = adjointtensorindices(A, poA) -# # _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), B) -# # elseif CA == :N && CB == :C -# # poB = adjointtensorindices(B, poB) -# # _similarstructure_from_indices(T, poA, poB, p1, p2, A, adjoint(B)) -# # else -# # poA = adjointtensorindices(A, poA) -# # poB = adjointtensorindices(B, poB) -# # _similarstructure_from_indices(T, poA, poB, p1, p2, adjoint(A), adjoint(B)) -# # end -# # end - -# function _similarstructure_from_indices(::Type{T}, p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, -# t::AbstractTensorMap{S}) where {T,S<:IndexSpace,N₁, -# N₂} -# cod = ProductSpace{S,N₁}(space.(Ref(t), p1)) -# dom = ProductSpace{S,N₂}(dual.(space.(Ref(t), p2))) -# return dom → cod -# end -# function _similarstructure_from_indices(::Type{T}, oindA::IndexTuple, oindB::IndexTuple, -# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, -# tA::AbstractTensorMap{S}, -# tB::AbstractTensorMap{S}) where {T,S<:IndexSpace,N₁, -# N₂} -# spaces = (space.(Ref(tA), oindA)..., space.(Ref(tB), oindB)...) -# cod = ProductSpace{S,N₁}(getindex.(Ref(spaces), p1)) -# dom = ProductSpace{S,N₂}(dual.(getindex.(Ref(spaces), p2))) -# return dom → cod -# end - -function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, - pC::Index2Tuple, - A::AbstractTensorMap{S}, pA::Index2Tuple, - conjA::Symbol, - B::AbstractTensorMap{S}, pB::Index2Tuple, - conjB::Symbol, - α::Number, β::Number) where {S,N₁,N₂} - p = linearize(pC) - pl = ntuple(n -> p[n], N₁) - pr = ntuple(n -> p[N₁ + n], N₂) - - if conjA == :C - pA = adjointtensorindices(A, pA) - A = A' - elseif conjA != :N - throw(ArgumentError("unknown conjugation flag $conjA")) - end - - if conjB == :C - pB = adjointtensorindices(B, pB) - B = B' - elseif conjB != :N - throw(ArgumentError("unknown conjugation flag $conjB")) - end - - contract!(α, A, B, β, C, pA[1], pA[2], pB[2], pB[1], pl, pr) - return C - - # if conjA == :N && conjB == :N - # contract!(α, tA, tB, β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) - # elseif conjA == :N && conjB == :C - # pB[2] = adjointtensorindices(tB, pB[2]) - # pB[1] = adjointtensorindices(tB, pB[1]) - # contract!(α, tA, tB', β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) - # elseif conjA == :C && conjB == :N - # pA[1] = adjointtensorindices(tA, pA[1]) - # pA[2] = adjointtensorindices(tA, pA[2]) - # contract!(α, tA', tB, β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) - # elseif conjA == :C && conjB == :C - # pA[1] = adjointtensorindices(tA, pA[1]) - # pA[2] = adjointtensorindices(tA, pA[2]) - # pB[2] = adjointtensorindices(tB, pB[2]) - # pB[1] = adjointtensorindices(tB, pB[1]) - # contract!(α, tA', tB', β, tC, pA[1], pA[2], pB[2], pB[1], pl, pr) - # else - # error("unknown conjugation flags: $conjA and $conjB") - # end - # return tC -end - -function TO.tensoradd_type(TC, ::Index2Tuple{N₁,N₂}, ::AbstractTensorMap{S}, - ::Symbol) where {S,N₁,N₂} - return tensormaptype(S, N₁, N₂, TC) -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 - else - return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pC), :N) - end -end - -function TO.tensorcontract_type(TC, ::Index2Tuple{N₁,N₂}, - ::AbstractTensorMap{S}, pA, conjA, - ::AbstractTensorMap{S}, pB, conjB) where {S,N₁,N₂} - return tensormaptype(S, N₁, N₂, TC) -end - -function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, - A::AbstractTensorMap{S}, pA::Index2Tuple, - conjA, B::AbstractTensorMap, - pB::Index2Tuple, conjB) where {S,N₁,N₂} - spaces1 = conjA == :N ? space.(Ref(A), pA[1]) : - space.(Ref(A'), adjointtensorindices(A, pA[1])) - spaces2 = conjB == :N ? space.(Ref(B), pB[2]) : - space.(Ref(B'), adjointtensorindices(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 -end - -TO.tensorstructure(t::AbstractTensorMap) = space(t) -function TO.tensorstructure(::AbstractTensorMap, iA::Int, conjA::Symbol) - return conjA == :N ? space(A, iA) : space(A', iA) -end - -function TO.tensoralloc(ttype::Type{<:AbstractTensorMap}, structure, istemp=false) - return TensorMap(undef, scalartype(ttype), structure) -end From 6c94ed1e24a498e69589821d3d9ebfb0394ad336 Mon Sep 17 00:00:00 2001 From: Jutho Date: Sun, 6 Aug 2023 01:32:45 +0200 Subject: [PATCH 27/57] make planar work again; all MPSKit tests work (after some changes) --- Project.toml | 2 +- src/TensorKit.jl | 1 + src/planar/analyzers.jl | 22 ++++-- src/planar/macros.jl | 125 ++++++++++++++++++++++---------- src/planar/planaroperations.jl | 5 +- src/planar/postprocessors.jl | 118 ++++++++++++++++-------------- src/planar/preprocessors.jl | 61 ++++++++-------- src/tensors/linalg.jl | 4 +- src/tensors/tensoroperations.jl | 18 ++--- src/tensors/vectorinterface.jl | 7 +- 10 files changed, 221 insertions(+), 142 deletions(-) diff --git a/Project.toml b/Project.toml index 75941883..92a497d8 100644 --- a/Project.toml +++ b/Project.toml @@ -17,7 +17,7 @@ WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" HalfIntegers = "1" LRUCache = "1.0.2" Strided = "2" -TensorOperations = "4" +TensorOperations = "4.0.2" TupleTools = "1.1" WignerSymbols = "1,2" julia = "1.6 - 1" diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 81fc8882..c2786f50 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -194,6 +194,7 @@ include("tensors/braidingtensor.jl") # # Planar macros and related functionality # #----------------------------------------- @nospecialize +using Base.Meta: isexpr include("planar/analyzers.jl") include("planar/preprocessors.jl") include("planar/postprocessors.jl") diff --git a/src/planar/analyzers.jl b/src/planar/analyzers.jl index cd1125d4..5cb577b7 100644 --- a/src/planar/analyzers.jl +++ b/src/planar/analyzers.jl @@ -1,3 +1,7 @@ +# Construct a list of all the ways in which the open indices of a tensor expression can be +# given a planar (cyclic) ordering. The list contains a representative for all inequivalent +# classes of planar orderings, where two orderings are equivalent if they are related by a +# cyclic permutation of the indices. function get_possible_planar_indices(ex) if !TO.istensorexpr(ex) return [[]] @@ -5,7 +9,7 @@ function get_possible_planar_indices(ex) _,leftind,rightind = TO.decomposegeneraltensor(ex) ind = planar_unique2(vcat(leftind, reverse(rightind))) return length(ind) == length(unique(ind)) ? Any[ind] : Any[] - elseif ex.head == :call && (ex.args[1] == :+ || ex.args[1] == :-) + elseif isexpr(ex, :call) && (ex.args[1] == :+ || ex.args[1] == :-) inds = get_possible_planar_indices(ex.args[2]) keep = fill(true, length(inds)) for i = 3:length(ex.args) @@ -20,8 +24,9 @@ function get_possible_planar_indices(ex) any(keep) || break # give up early if keep is all false end return inds[keep] - elseif ex.head == :call && ex.args[1] == :* - @assert length(ex.args) == 3 + elseif isexpr(ex, :call) && ex.args[1] == :* + length(ex.args) == 3 || + error("unexpected error occured: contraction should have been decomposed into tree by now") inds1 = get_possible_planar_indices(ex.args[2]) inds2 = get_possible_planar_indices(ex.args[3]) inds = Any[] @@ -36,7 +41,11 @@ function get_possible_planar_indices(ex) end end -# remove double indices (trace indices) from cyclic set +# Remove double indices (trace indices) from cyclic set, but only if the trace can be +# performed planarly. This requires that any two indices to be traced are only separated by +# other indices that are themselves also to be traced amongst each other. Hence, there is +# an innermost pair of indices to be traced, which can thus be removed first, and then the +# process can be repeated until no more indices can be traced. function planar_unique2(allind) oind = collect(allind) removing = true @@ -57,7 +66,10 @@ function planar_unique2(allind) return oind end -# remove intersection (contraction indices) from two cyclic sets +# Remove intersection (contraction indices) from two cyclic sets, again only if the +# contraction can be performed planarly. This requires that all contraction indices of both +# tensors are adjacent, and that they appear in opposite order in the two sets (which should +# be interpreted in the cyclic sense). function possible_planar_complements(ind1, ind2) # quick return path (isempty(ind1) || isempty(ind2)) && return Any[(ind1, ind2, Any[], Any[])] diff --git a/src/planar/macros.jl b/src/planar/macros.jl index 113f0c87..09dbe6a7 100644 --- a/src/planar/macros.jl +++ b/src/planar/macros.jl @@ -1,62 +1,113 @@ # NEW MACROS: @planar and @plansor -macro planar(ex::Expr) - return esc(planar_parser(ex)) +macro planar(args::Vararg{Expr}) + isempty(args) && throw(ArgumentError("No arguments passed to `planar`")) + + planarexpr = args[end] + kwargs = TO.parse_tensor_kwargs(args[1:(end - 1)]) + parser = planarparser(planarexpr, kwargs... ) + + return esc(parser(planarexpr)) end -function planar_parser(ex::Expr) +function planarparser(planarexpr, kwargs...) parser = TO.TensorParser() - braidingtensors = Vector{Any}() - pop!(parser.preprocessors) # remove TO.extracttensorobjects push!(parser.preprocessors, _conj_to_adjoint) push!(parser.preprocessors, _extract_tensormap_objects) + + temporaries = Vector{Symbol}() + push!(parser.postprocessors, ex->_annotate_temporaries(ex, temporaries)) + push!(parser.postprocessors, ex->_free_temporaries(ex, temporaries)) + push!(parser.postprocessors, _insert_planar_operations) + + for kw in kwargs + name, val = kw + + if name == :order + isexpr(val, :tuple) || + throw(ArgumentError("Invalid use of `order`, should be `order=(...,)`")) + indexorder = map(normalizeindex, val.args) + parser.contractiontreebuilder = network -> TO.indexordertree(network, indexorder) + + elseif name == :contractcheck + val isa Bool || + throw(ArgumentError("Invalid use of `contractcheck`, should be `contractcheck=bool`.")) + val && push!(parser.preprocessors, ex -> TO.insertcontractionchecks(ex)) + + elseif name == :costcheck + val in (:warn, :cache) || + throw(ArgumentError("Invalid use of `costcheck`, should be `costcheck=warn` or `costcheck=cache`")) + parser.contractioncostcheck = val + elseif name == :opt + if val isa Bool && val + optdict = TO.optdata(planarexpr) + elseif val isa Expr + optdict = TO.optdata(val, planarexpr) + else + throw(ArgumentError("Invalid use of `opt`, should be `opt=true` or `opt=OptExpr`")) + end + parser.contractiontreebuilder = network -> TO.optimaltree(network, optdict)[1] + elseif name == :backend + val isa Symbol || + throw(ArgumentError("Backend should be a symbol.")) + push!(parser.postprocessors, ex -> insert_operationbackend(ex, val)) + elseif name == :allocator + val isa Symbol || + throw(ArgumentError("Allocator should be a symbol.")) + push!(parser.postprocessors, ex -> TO.insert_allocatorbackend(ex, val)) + else + throw(ArgumentError("Unknown keyword argument `name`.")) + end + end + braidingtensors = Vector{Any}() + push!(parser.preprocessors, _construct_braidingtensors) treebuilder = parser.contractiontreebuilder treesorter = parser.contractiontreesorter - push!(parser.preprocessors, ex->TO.processcontractions(ex, treebuilder, treesorter)) + costcheck = parser.contractioncostcheck + push!(parser.preprocessors, ex->TO.processcontractions(ex, treebuilder, treesorter, costcheck)) + parser.contractioncostcheck = nothing push!(parser.preprocessors, ex->_check_planarity(ex)) - temporaries = Vector{Symbol}() push!(parser.preprocessors, ex->_decompose_planar_contractions(ex, temporaries)) - - pop!(parser.postprocessors) # remove TO.addtensoroperations - push!(parser.postprocessors, ex->_update_temporaries(ex, temporaries)) - push!(parser.postprocessors, ex->_annotate_temporaries(ex, temporaries)) - push!(parser.postprocessors, _add_modules) - return parser(ex) + + return parser end -macro plansor(ex::Expr) - return esc(plansor_parser(ex)) +macro plansor(args::Vararg{Expr}) + isempty(args) && throw(ArgumentError("No arguments passed to `planar`")) + + planarexpr = args[end] + kwargs = TO.parse_tensor_kwargs(args[1:(end - 1)]) + return esc(_plansor(planarexpr, kwargs...)) end -function plansor_parser(ex) - inputtensors = TO.getinputtensorobjects(ex) - newtensors = TO.getnewtensorobjects(ex) +function _plansor(expr, kwargs...) + inputtensors = TO.getinputtensorobjects(expr) + newtensors = TO.getnewtensorobjects(expr) # find the first non-braiding tensor to determine the braidingstyle targetobj = inputtensors[findfirst(x->x != :τ, inputtensors)] - targetsym = gensym() - - ex = TO.replacetensorobjects(ex) do obj, leftind, rightind - obj == targetobj ? targetsym : obj + if !isa(targetobj, Symbol) + targetsym = gensym(string(targetobj)) + expr = TO.replacetensorobjects(expr) do obj, leftind, rightind + obj == targetobj ? targetsym : obj + end + args = Any[(:($targetsym = $targetobj))] + else + targetsym = targetobj + args = Any[] end - defaultparser = TO.TensorParser() - insert!(defaultparser.preprocessors, 3, _remove_braidingtensors) - defaultex = defaultparser(ex) - planarex = planar_parser(ex) + tparser = TO.tensorparser(expr, kwargs...) + pparser = planarparser(expr, kwargs...) + insert!(tparser.preprocessors, 5, _remove_braidingtensors) + tensorex = tparser(expr) + planarex = pparser(expr) - ex = Expr(:block) - push!(ex.args, Expr(:(=), targetsym, targetobj)) - push!(ex.args, :(if BraidingStyle(sectortype($targetsym)) isa Bosonic - $(defaultex) - else - $(planarex) - end)) - if targetobj in newtensors - push!(ex.args, Expr(:(=), targetobj, targetsym)) - push!(ex.args, newtensors[end]) + push!(args, Expr(:if, :(BraidingStyle(sectortype($targetsym)) isa Bosonic), tensorex, planarex)) + if !isa(targetobj, Symbol) && targetobj ∈ newtensors + push!(args, :($targetobj = $targetsym)) end - return ex + return Expr(:block, args...) end diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index e26dab22..8ac66c18 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -1,6 +1,7 @@ # planar versions of tensor operations add!, trace! and contract! -function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, - pA::Index2Tuple{N₁,N₂}, α, β) where {S,N₁,N₂} +function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, pA::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, + α, β) where {S,N₁,N₂} return add_transpose!(α, A, β, C, pA...) end diff --git a/src/planar/postprocessors.jl b/src/planar/postprocessors.jl index 57d65414..63f234b7 100644 --- a/src/planar/postprocessors.jl +++ b/src/planar/postprocessors.jl @@ -1,82 +1,94 @@ # Additional postprocessors for @planar and @plansor -# since temporaries were taken out in preprocessing by _decompose_planar_contractions, they -# are not identified by the parsing step of TensorOperations, so we have to manually fix this -# Step 1: we have to find the new name that TO.tensorify assigned to these temporaries -# since it parses `tmp[] := a[] * b[]` as `newtmp = similar...; tmp = contract!(.... , newtmp, ...)` - -const _PLANAR_OPERATIONS = (:planaradd!, :planarcontract!, :planartrace!) -const _TENSOR_OPERATIONS = (:tensoradd!, :tensorcontract!, :tensortrace!) - -_is_tensoroperation(ex::Expr) = ex.head == :call && (ex.args[1] in _TENSOR_OPERATIONS) -_is_tensoroperation(ex) = false - -function _update_temporaries(ex, temporaries) - if ex isa Expr && ex.head == :(=) +# Temporaries were explicitly created by _decompose_planar_contractions and were thus +# instantiated as if they were new output tensors rather than temporary tensors; we need +# to correct for this by adding the `istemp = true` flag. +function _annotate_temporaries(ex, temporaries) + if isexpr(ex, :(=)) && isexpr(ex.args[2], :call) && + ex.args[2].args[1] ∈ (:tensoralloc_add, :tensoralloc_contract) lhs = ex.args[1] i = findfirst(==(lhs), temporaries) if i !== nothing rhs = ex.args[2] - if _is_tensoroperation(rhs) - temporaries[i] = rhs.args[2] - else - temporaries[i] = rhs - # error("lhs = $lhs , rhs = $rhs") - end + # add `istemp = true` flag + newrhs = Expr(:call, rhs.args[1:end-1]..., true) + return Expr(:(=), lhs, newrhs) end elseif ex isa Expr - for a in ex.args - _update_temporaries(a, temporaries) - end + return Expr(ex.head, [_annotate_temporaries(a, temporaries) for a in ex.args]...) end return ex end -# Step 2: we find `newtmp = similar_from_...` and replace with `newtmp = cached_similar_from...` -# this is not necessary anymore? -function _annotate_temporaries(ex, temporaries) - # if ex isa Expr && ex.head == :(=) - # lhs = ex.args[1] - # i = findfirst(==(lhs), temporaries) - # if i !== nothing - # rhs = ex.args[2] - # if !(rhs isa Expr && rhs.head == :call && rhs.args[1] == :similar_from_indices) - # @error "lhs = $lhs , rhs = $rhs" - # end - # newrhs = Expr(:call, :cached_similar_from_indices, - # QuoteNode(lhs), rhs.args[2:end]...) - # return Expr(:(=), lhs, newrhs) - # end - # elseif ex isa Expr - # return Expr(ex.head, [_annotate_temporaries(a, temporaries) for a in ex.args]...) - # end - return ex +function _contains_arg(ex, arg) + if ex isa Expr + return any(_contains_arg(a, arg) for a in ex.args) + else + return ex == arg + end end -# add correct modules (`GlobalRef`) to various functions -function _add_modules(ex::Expr) - if ex.head == :call - if ex.args[1] == :tensoradd! +function _free_temporaries(ex, temporaries) + if isexpr(ex, :block) + newargs = copy(ex.args) + for t in temporaries + i = findlast(e -> _contains_arg(e, t), newargs) + @assert !isnothing(i) + if i == length(newargs) + @assert isexpr(newargs[i], :(=)) + lhs = newargs[i].args[1] + push!(newargs, Expr(:call, :tensorfree!, t)) + push!(newargs, lhs) + else + newargs = insert!(newargs, i+1, Expr(:call, :tensorfree!, t)) + end + end + return Expr(:block, newargs...) + end +end + +# Replace the tensor operations created by `TO.instantiate` with the corresponding +# planar operations, immediately inserting them with `GlobalRef`. +function _insert_planar_operations(ex) + if isexpr(ex, :call) + if ex.args[1] == GlobalRef(TensorOperations, :tensoradd!) conjA = popat!(ex.args, 5) @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planaradd!)), - map(_add_modules, ex.args[2:end])...) - elseif ex.args[1] == :tensorcontract! + map(_insert_planar_operations, ex.args[2:end])...) + elseif ex.args[1] == GlobalRef(TensorOperations, :tensorcontract!) conjB = popat!(ex.args, 9) conjA = popat!(ex.args, 6) @assert conjA == conjB == :(:N) "conj flag should be `:N` ($conjA), ($conjB)" return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planarcontract!)), - map(_add_modules, ex.args[2:end])...) - elseif ex.args[1] == :tensortrace! + map(_insert_planar_operations, ex.args[2:end])...) + elseif ex.args[1] == GlobalRef(TensorOperations, :tensortrace!) conjA = popat!(ex.args, 6) @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planartrace!)), - map(_add_modules, ex.args[2:end])...) + map(_insert_planar_operations, ex.args[2:end])...) elseif ex.args[1] in TensorOperations.tensoroperationsfunctions return Expr(ex.head, GlobalRef(TensorOperations, ex.args[1]), - map(_add_modules, ex.args[2:end])...) + map(_insert_planar_operations, ex.args[2:end])...) end + elseif isa(ex, Expr) + return Expr(ex.head, (_insert_planar_operations(e) for e in ex.args)...) end - return Expr(ex.head, (_add_modules(e) for e in ex.args)...) + return ex end -_add_modules(ex) = ex + +const _PLANAR_OPERATIONS = (:planaradd!, :planarcontract!, :planartrace!) + +# Mimick `TO.insert_operationbackend` for planar operations. +function insert_operationbackend(ex, backend) + if isexpr(ex, :call) && ex.args[1] isa GlobalRef && + ex.args[1].mod == TensorKit && + ex.args[1].name ∈ _PLANAR_OPERATIONS + b = Backend{backend}() + return Expr(:call, ex.args..., b) + elseif isa(ex, Expr) + return Expr(ex.head, (insert_operationbackend(e, backend) for e in ex.args)...) + else + return ex + end +end \ No newline at end of file diff --git a/src/planar/preprocessors.jl b/src/planar/preprocessors.jl index 8c8c22c2..498a04fb 100644 --- a/src/planar/preprocessors.jl +++ b/src/planar/preprocessors.jl @@ -2,16 +2,17 @@ @noinline not_planar_err(ex) = throw(ArgumentError("not a planar diagram expression: $ex")) # Preprocessors used by `@planar` and `@plansor` -function _conj_to_adjoint(ex::Expr) - if ex.head == :call && ex.args[1] == :conj && TO.istensor(ex.args[2]) +function _conj_to_adjoint(ex) + if isexpr(ex, :call) && ex.args[1] == :conj && TO.istensor(ex.args[2]) obj, leftind, rightind = TO.decomposetensor(ex.args[2]) return Expr(:typed_vcat, Expr(TO.prime, obj), Expr(:tuple, rightind...), Expr(:tuple, leftind...)) - else + elseif ex isa Expr return Expr(ex.head, [_conj_to_adjoint(a) for a in ex.args]...) + else + return ex end end -_conj_to_adjoint(ex) = ex # replacement of TensorOperations functionality: # adds checks for matching number of domain and codomain indices @@ -26,8 +27,8 @@ function _extract_tensormap_objects(ex) @assert !any(_is_adjoint, newtensors) existingtensors = unique!(vcat(inputtensors, outputtensors)) alltensors = unique!(vcat(existingtensors, newtensors)) - tensordict = Dict{Any,Any}(a => gensym() for a in alltensors) - pre = Expr(:block, [Expr(:(=), tensordict[a], a) for a in existingtensors]...) + tensordict = Dict{Any,Any}(a => gensym(string(a)) for a in alltensors if !(a isa Symbol)) + pre = Expr(:block, [Expr(:(=), tensordict[a], a) for a in existingtensors if !(a isa Symbol)]...) pre2 = Expr(:block) ex = TO.replacetensorobjects(ex) do obj, leftind, rightind _is_adj = _is_adjoint(obj) @@ -46,20 +47,20 @@ function _extract_tensormap_objects(ex) errorstr2 = " for $objstr." checksize = quote $nlsym, $nrsym = numout($newobj), numin($newobj) - ($nlsym == $nl && $nrsym == $nr) || + (numout($newobj) == $nl && $numin($newobj)== $nr) || throw(IndexError($errorstr1 * string(($nlsym, $nrsym)) * $errorstr2)) end push!(pre2.args, checksize) end return _is_adj ? _add_adjoint(newobj) : newobj end - post = Expr(:block, [Expr(:(=), a, tensordict[a]) for a in newtensors]...) + post = Expr(:block, [Expr(:(=), a, tensordict[a]) for a in newtensors if !(a isa Symbol)]...) pre = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre) pre2 = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre2) post = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), post) return Expr(:block, pre, pre2, ex, post) end -_is_adjoint(ex) = isa(ex, Expr) && ex.head == TO.prime +_is_adjoint(ex) = isexpr(ex, TO.prime) _remove_adjoint(ex) = _is_adjoint(ex) ? ex.args[1] : ex _add_adjoint(ex) = Expr(TO.prime, ex) @@ -188,7 +189,8 @@ end _construct_braidingtensors(x) = x # used by non-planar parser of `@plansor`: remove explicit braiding tensors -function _remove_braidingtensors(ex::Expr) +function _remove_braidingtensors(ex) + ex isa Expr || return ex outgoing = [] if TO.isdefinition(ex) || TO.isassignment(ex) @@ -293,11 +295,11 @@ function _remove_braidingtensors(ex::Expr) ex = TO.replaceindices(i -> get(indexmap, i, i), ex) return _purge_braidingtensors(ex) end -_remove_braidingtensors(x) = x -function _purge_braidingtensors(ex::Expr) # actually remove the braidingtensors +function _purge_braidingtensors(ex) # actually remove the braidingtensors + ex isa Expr || return ex args = collect(filter(ex.args) do a - if a isa Expr && a.head == :call && a.args[1] == :conj + if isexpr(a, :call) && a.args[1] == :conj a = a.args[2] end if a isa Expr && TO.istensor(a) && _remove_adjoint(TO.gettensorobject(a)) == :τ @@ -310,16 +312,17 @@ function _purge_braidingtensors(ex::Expr) # actually remove the braidingtensors end) # multiplication with only a single argument is (rightfully) seen as invalid syntax - if ex.head == :call && args[1] == :* && length(args) == 2 + if isexpr(ex, :call) && args[1] == :* && length(args) == 2 return _purge_braidingtensors(args[2]) else return Expr(ex.head, map(_purge_braidingtensors, args)...) end end -_purge_braidingtensors(x) = x -function _check_planarity(ex::Expr) - if ex.head == :macrocall && ex.args[1] == Symbol("@notensor") +function _check_planarity(ex) + ex isa Expr || return ex + if isexpr(ex, :macrocall) && ex.args[1] == Symbol("@notensor") + return ex elseif TO.isassignment(ex) || TO.isdefinition(ex) lhs, rhs = TO.getlhs(ex), TO.getrhs(ex) if TO.istensorexpr(rhs) @@ -341,13 +344,12 @@ function _check_planarity(ex::Expr) end return ex end -_check_planarity(ex) = ex # decompose contraction trees in order to fix index order of temporaries # to ensure that planarity is guaranteed _decompose_planar_contractions(ex, temporaries) = ex function _decompose_planar_contractions(ex::Expr, temporaries) - if ex.head == :macrocall && ex.args[1] == Symbol("@notensor") + if isexpr(ex, :macrocall) && ex.args[1] == Symbol("@notensor") return ex end if TO.isassignment(ex) || TO.isdefinition(ex) @@ -365,14 +367,10 @@ function _decompose_planar_contractions(ex::Expr, temporaries) rhs = _extract_contraction_pairs(ex, (Any[], Any[]), pre, temporaries) return Expr(:block, pre..., rhs) end - if ex.head == :block + if isexpr(ex, :block) return Expr(ex.head, [_decompose_planar_contractions(a, temporaries) for a in ex.args]...) end - if ex.head == :for || ex.head == :function - return Expr(ex.head, ex.args[1], - _decompose_planar_contractions(ex.args[2], temporaries)) - end return ex end @@ -392,8 +390,8 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) else return rhs end - elseif rhs.head == :call && rhs.args[1] == :* - @assert length(rhs.args) == 3 + elseif isexpr(rhs, :call) && rhs.args[1] == :* + # @assert length(rhs.args) == 3 # has already been checked in _check_planarity if lhs isa Expr _, leftind, rightind = TO.decomposetensor(lhs) @@ -417,7 +415,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) isempty(rhs_inds) || break end ind1, ind2, oind1, oind2, cind1, cind2 = only(rhs_inds) # inds_rhs should hold exactly one match - if all(in(leftind), oind2) && all(in(rightind), oind1) # reverse order + if all(in(leftind), oind2) || all(in(rightind), oind1) # reverse order a1 = _extract_contraction_pairs(rhs.args[3], (oind2, reverse(cind2)), pre, temporaries) a2 = _extract_contraction_pairs(rhs.args[2], (cind1, reverse(oind1)), pre, temporaries) oind1, oind2 = oind2, oind1 @@ -426,6 +424,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) a1 = _extract_contraction_pairs(rhs.args[2], (oind1, reverse(cind1)), pre, temporaries) a2 = _extract_contraction_pairs(rhs.args[3], (cind2, reverse(oind2)), pre, temporaries) end + # @show a1, a2, oind1, oind2 if TO.isscalarexpr(a1) || TO.isscalarexpr(a2) rhs = Expr(:call, :*, a1, a2) @@ -437,7 +436,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) return newlhs end - # note that index order in _extract... is only a suggestion, now we have actual index order + # note that index order in `lhs` is only a suggestion, now we have actual index order _, l1, r1, = TO.decomposegeneraltensor(a1) _, l2, r2, = TO.decomposegeneraltensor(a2) if all(in(r1), oind1) && all(in(l2), oind2) # reverse order @@ -445,6 +444,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) ind1, ind2 = ind2, ind1 oind1, oind2 = oind2, oind1 end + # @show a1, a2, oind1, oind2 if lhs isa Tuple rhs = Expr(:call, :*, a1, a2) s = gensym() @@ -457,9 +457,6 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) if leftind == oind1 && rightind == reverse(oind2) rhs = Expr(:call, :*, a1, a2) return rhs - elseif leftind == oind2 && rightind == reverse(oind1) # probably this can not happen anymore - rhs = Expr(:call, :*, a2, a1) - return rhs else rhs = Expr(:call, :*, a1, a2) s = gensym() @@ -470,7 +467,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) return newlhs end end - elseif rhs.head == :call && rhs.args[1] ∈ (:+, :-) + elseif isexpr(rhs, :call) && rhs.args[1] ∈ (:+, :-) args = [_extract_contraction_pairs(a, lhs, pre, temporaries) for a in rhs.args[2:end]] return Expr(rhs.head, rhs.args[1], args...) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 8b6c9410..9368a241 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -177,8 +177,8 @@ function LinearAlgebra.adjoint!(tdst::AbstractTensorMap, end # Basic vector space methods: recycle VectorInterface implementation -LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) = scale!(t, α) -LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) = scale!(t, α) +LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) = iszero(α) ? zerovector!(t) : scale!(t, α) +LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) = iszero(α) ? zerovector!(t) : scale!(t, α) LinearAlgebra.mul!(t1::AbstractTensorMap, t2::AbstractTensorMap, α::Number) = scale!(t1, t2, α) LinearAlgebra.mul!(t1::AbstractTensorMap, α::Number, t2::AbstractTensorMap) = scale!(t1, t2, α) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index fea99056..bd8a4cfa 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -5,12 +5,12 @@ function TO.tensorstructure(t::AbstractTensorMap, iA::Int, conjA::Symbol) return conjA == :N ? space(t, iA) : conj(space(t, iA)) end -function TO.tensoralloc(ttype::Type{<:AbstractTensorMap}, structure, istemp=false, - backend::Backend...) - M = storagetype(ttype) - return TensorMap(structure) do d - return TO.tensoralloc(M, d, istemp, backend...) +function TO.tensoralloc(::Type{TT}, structure, istemp=false, + backend::Backend...) where {TT<:AbstractTensorMap} + function blockallocator(d) + return TO.tensoralloc(storagetype(TT), d, istemp, backend...) end + return TensorMap(blockallocator, structure) end function TO.tensorfree!(t::AbstractTensorMap, backend::Backend...) @@ -31,8 +31,8 @@ function _canonicalize(p::Index2Tuple, ::AbstractTensorMap) end # tensoradd! -function TO.tensoradd!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, pC::Index2Tuple, conjA::Symbol, +function TO.tensoradd!(C::AbstractTensorMap{S}, pC::Index2Tuple, + A::AbstractTensorMap{S}, conjA::Symbol, α::Number, β::Number, backend::Backend...) where {S} if conjA == :N A′ = A @@ -44,7 +44,7 @@ function TO.tensoradd!(C::AbstractTensorMap{S}, throw(ArgumentError("unknown conjugation flag $conjA")) end # TODO: novel syntax for tensoradd!? - # tensoradd!(C, A′, pC′, α, β, backend...) + # tensoradd!(C, pC′, A′, α, β, backend...) add!(α, A′, β, C, pC′[1], pC′[2]) return C end @@ -62,7 +62,7 @@ function TO.tensoradd_structure(pC::Index2Tuple{N₁,N₂}, dom = ProductSpace{S,N₂}(dual.(space.(Ref(A), pC[2]))) return dom → cod else - return TO.tensoradd_structure(adjoint(A), adjointtensorindices(A, pC), :N) + return TO.tensoradd_structure(adjointtensorindices(A, pC), adjoint(A), :N) end end diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index 315e0a9e..141d2479 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -10,7 +10,12 @@ VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = scalartype(storagetyp function VectorInterface.zerovector(t::AbstractTensorMap, ::Type{S}) where {S<:Number} return zero!(similar(t, S)) end -VectorInterface.zerovector!(t::AbstractTensorMap) = zero!(t) +function VectorInterface.zerovector!(t::AbstractTensorMap) + for (c, b) in blocks(t) + zerovector!(b) + end + return t +end VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) # scale, scale! & scale!! From 866e5e011564b7be1a7a84e75e075dc10dfd23ff Mon Sep 17 00:00:00 2001 From: Jutho Date: Thu, 10 Aug 2023 12:53:10 +0200 Subject: [PATCH 28/57] part 1 - still wip --- src/auxiliary/deprecate.jl | 66 +----- src/planar/planaroperations.jl | 50 +++-- src/tensors/indexmanipulations.jl | 355 +++++++++++++++++++++++------- src/tensors/tensoroperations.jl | 297 ++++++------------------- 4 files changed, 377 insertions(+), 391 deletions(-) diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index 8a90cf57..3faf19c4 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -1,63 +1,7 @@ -@noinline function Base.getindex(::ComplexNumbers, I::Type{<:Group}) - S = Rep[I] - if repr(S) == "GradedSpace[Irrep[$I]]" - Base.depwarn("`ℂ[$I]` is deprecated because $I represents a group rather than its representations, use `ℂ[Irrep[$I]]`, `Rep[$I]` or `GradedSpace[Irrep[$I]]` instead.", - ((Base.Core).Typeof(Base.getindex)).name.mt.name) - else - Base.depwarn("`ℂ[$I]` is deprecated because $I represents a group rather than its representations, use `ℂ[Irrep[$I]]`, `Rep[$I]`, `$S` or `GradedSpace[Irrep[$I]]` instead.", - ((Base.Core).Typeof(Base.getindex)).name.mt.name) - end - return Rep[I] -end - -@noinline function ℤ₂(args...) - Base.depwarn("`ℤ₂(args...)` is deprecated, use `Z2Irrep(args...)` or ``Irrep[ℤ₂](args...)` instead.", ((Base.Core).Typeof(ℤ₂)).name.mt.name) - Irrep[ℤ₂](args...) -end -@noinline function ℤ₃(args...) - Base.depwarn("`ℤ₃(args...)` is deprecated, use `Z3Irrep(args...)` or ``Irrep[ℤ₃](args...)` instead.", ((Base.Core).Typeof(ℤ₃)).name.mt.name) - Irrep[ℤ₃](args...) -end -@noinline function ℤ₄(args...) - Base.depwarn("`ℤ₄(args...)` is deprecated, use `Z4Irrep(args...)` or ``Irrep[ℤ₄](args...)` instead.", ((Base.Core).Typeof(ℤ₄)).name.mt.name) - Irrep[ℤ₄](args...) -end -@noinline function U₁(args...) - Base.depwarn("`U₁(args...)` is deprecated, use `U1Irrep(args...)` or ``Irrep[U₁](args...)` instead.", ((Base.Core).Typeof(U₁)).name.mt.name) - Irrep[U₁](args...) -end -@noinline function CU₁(args...) - Base.depwarn("`CU₁(args...)` is deprecated, use `CU1Irrep(args...)` or ``Irrep[CU₁](args...)` instead.", ((Base.Core).Typeof(CU₁)).name.mt.name) - Irrep[CU₁](args...) -end -@noinline function SU₂(args...) - Base.depwarn("`SU₂(args...)` is deprecated, use `SU2Irrep(args...)` or ``Irrep[SU₂](args...)` instead.", ((Base.Core).Typeof(SU₂)).name.mt.name) - Irrep[SU₂](args...) -end - -Base.@deprecate(permuteind!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, p1, p2), - permute!(tdst, tsrc, p1, p2)) - -function Base.getindex(::Type{GradedSpace}, ::Type{I}) where {I<:Sector} - @warn "`getindex(::Type{GradedSpace}, I::Type{<:Sector})` is deprecated, use `ℂ[I]`, `Vect[I]`, or, if `I == Irrep[G]`, `Rep[G]` instead." maxlog = 1 - return Vect[I] -end - -function Base.getindex(::ComplexNumbers, d1::Pair{I, Int}, dims::Pair{I, Int}...) where {I<:Sector} - @warn "`ℂ[s1=>n1, s2=>n2, ...]` is deprecated, use `ℂ[I](s1=>n1, s2=>n2, ...)`, `Vect[I](s1=>n1, s2=>n2, ...)` instead with `I = typeof(s1)`." maxlog = 1 - return Vect[I](d1, dims...) -end - -function Base.getindex(::RealNumbers, d::Int) - @warn "`ℝ[d]` is deprecated, use `ℝ^d` or `CartesianSpace(d)`." maxlog = 1 - return ℝ^d -end - -function Base.getindex(::ComplexNumbers, d::Int) - @warn "`ℂ[d]` is deprecated, use `ℂ^d` or `ComplexSpace(d)`." maxlog = 1 - return ℂ^d -end - -import Base: eltype +import Base: eltype, transpose @deprecate eltype(T::Type{<:AbstractTensorMap}) scalartype(T) @deprecate eltype(t::AbstractTensorMap) scalartype(t) + +@deprecate permute(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) permutedims(t, (p1, p2); copy=copy) +@deprecate transpose(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) transpose(t, (p1, p2); copy=copy) +@deprecate braid(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple, levels; copy::Bool=false) braid(t, (p1, p2), levels; copy=copy) diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index 8ac66c18..3349545f 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -1,17 +1,17 @@ # planar versions of tensor operations add!, trace! and contract! -function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, pA::Index2Tuple{N₁,N₂}, +function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, - α, β) where {S,N₁,N₂} - return add_transpose!(α, A, β, C, pA...) + α, β, backend::Backend...) where {S,N₁,N₂} + return add_transpose!(C, A, p, α, β, backend...) end -function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N₂}, - A::AbstractTensorMap{S}, pA::Index2Tuple{N₃,N₃}, - α, β) where {S,N₁,N₂,N₃} +function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, q::Index2Tuple{N₃,N₃}, + α, β, backend::Backend...) where {S,N₁,N₂,N₃} if BraidingStyle(sectortype(S)) == Bosonic() - return tensortrace!(C, pC, A, pA, conjA, α, β) + return trace_permute!(C, A, p, q, α, β, backend...) end - + @boundscheck begin all(i -> space(A, pC[1][i]) == space(C, i), 1:N₁) || throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), @@ -23,43 +23,47 @@ function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), q1 = $(q1), q2 = $(q2)")) end - + if iszero(β) fill!(C, β) - elseif β != 1 + elseif !isone(β) rmul!(C, β) end - pdata = linearize(pC) for (f₁, f₂) in fusiontrees(A) - for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, pC..., pA...) - TO._trace!(α * coeff, A[f₁, f₂], true, C[f₁′, f₂′], pdata, pA...) + for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, p..., q...) + TO.tensortrace!(C[f₁′, f₂′], p, A[f₁, f₂], q, α * coeff, true, backend...) end end return C end -function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple{N₁,N₂}, - A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, pB::Index2Tuple, α, β) where {S,N₁,N₂} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, pAB::Index2Tuple{N₁,N₂}, + A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, + pB::Index2Tuple, α, β, backend::Backend...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, pA..., pB[2], pB[1], pC...) - + oindA, cindA = pA + cindB, oindB = pB + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, pAB...) + if oindA == codA && cindA == domA A′ = A else - A′ = TO.tensoralloc_add(scalartype(A), (oindA, cindA), A, :N) - add_transpose!(true, A, false, A′, oindA, cindA) + A′ = TO.tensoralloc_add(scalartype(A), (oindA, cindA), A, :N, true) + add_transpose!(A′, A, (oindA, cindA), true, false, backend...) end - + if cindB == codB && oindB == domB B′ = B else - B′ = TensorOperations.tensoralloc_add(scalartype(B), (cindB, oindB), B, :N) - add_transpose!(true, B, false, B′, cindB, oindB) + B′ = TensorOperations.tensoralloc_add(scalartype(B), (cindB, oindB), B, :N, true) + add_transpose!(B′, B, (cindB, oindB), true, false, backend...) end mul!(C, A′, B′, α, β) - + (oindA == codA && cindA == domA) || TO.tensorfree!(A′) + (cindB == codB && oindB == domB) || TO.tensorfree!(B′) + return C end diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 27cd0e7a..ec4c7145 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -1,140 +1,202 @@ # Index manipulations #--------------------- """ - permute(tsrc::AbstractTensorMap{S}, p1::NTuple{N₁, Int}, p2::NTuple{N₂, Int} = ()) - -> tdst::TensorMap{S, N₁, N₂} + permute!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, + (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}) where {S,N₁,N₂} + -> tdst -Permute the indices of `tsrc::AbstractTensorMap{S}` such that a new tensor -`tdst::TensorMap{S, N₁, N₂}` is obtained, with indices in `p1` playing the role of the -codomain or range of the map, and indices in `p2` indicating the domain. +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`](@ref) for creating a new tensor and [`add_permute!`](@ref) for a more general version. +""" +@propagate_inbounds function Base.permute!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap{S}, + p::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} + return add_permute!(tdst, tsrc, p, true, false) +end -To permute into an existing `tdst`, see [`add!`](@ref) """ -function permute(t::TensorMap{S}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=(); - copy::Bool = false) where {S, N₁, N₂} - cod = ProductSpace{S, N₁}(map(n->space(t, n), p1)) - dom = ProductSpace{S, N₂}(map(n->dual(space(t, n)), p2)) + permute(tsrc::AbstractTensorMap{S}, (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}; + copy::Bool=false) where {S,N₁,N₂} + -> tdst::TensorMap{S,N₁,N₂} + +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!](@ref) and [`add_permute!`](@ref) +""" +function permute(t::TensorMap{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₂)) # share data if possible if !copy - if p1 === codomainind(t) && p2 === domainind(t) + if p₁ === codomainind(t) && p₂ === domainind(t) return t - elseif has_shared_permute(t, p1, p2) + elseif has_shared_permute(t, p) return TensorMap(reshape(t.data, dim(cod), dim(dom)), cod, dom) end end # general case @inbounds begin - return add!(true, t, false, similar(t, cod←dom), p1, p2) + return permute!(similar(t, cod ← dom), t, (p₁, p₂)) end end - -function permute(t::AdjointTensorMap{S}, p1::IndexTuple, p2::IndexTuple=(); - copy::Bool = false) where {S} - p1′ = map(n->adjointtensorindex(t, n), p2) - p2′ = map(n->adjointtensorindex(t, n), p1) - adjoint(permute(adjoint(t), p1′, p2′; copy = copy)) +function permute(t::AdjointTensorMap{S}, (p₁, p₂)::Index2Tuple; + copy::Bool=false) where {S} + p₁′ = adjointtensorindices(t, p₂) + p₂′ = adjointtensorindices(t, p₁) + return adjoint(permute(adjoint(t), (p₁′, p₂′); copy=copy)) +end +function permute(t::AbstractTensorMap, p::IndexTuple; copy::Bool=false) + return permute(t, (p, ()); copy=copy) end -function has_shared_permute(t::TensorMap, p1, p2) - if p1 === codomainind(t) && p2 === domainind(t) +function has_shared_permute(t::TensorMap, (p₁, p₂)::Index2Tuple) + if p₁ === codomainind(t) && p₂ === domainind(t) return true elseif sectortype(t) === Trivial - stridet = i->stride(t[], i) - sizet = i->size(t[], i) - canfuse1, d1, s1 = TO._canfuse(sizet.(p1), stridet.(p1)) - canfuse2, d2, s2 = TO._canfuse(sizet.(p2), stridet.(p2)) + stridet = i -> stride(t[], i) + sizet = i -> size(t[], i) + canfuse1, d1, s1 = TO._canfuse(sizet.(p₁), stridet.(p₁)) + canfuse2, d2, s2 = TO._canfuse(sizet.(p₂), stridet.(p₂)) return canfuse1 && canfuse2 && s1 == 1 && (d2 == 1 || s2 == d1) else return false end end - -function has_shared_permute(t::AdjointTensorMap, p1, p2) - p1′ = adjointtensorindices(t, p2) - p2′ = adjointtensorindices(t, p1) - return has_shared_permute(t', p1′, p2′) +function has_shared_permute(t::AdjointTensorMap, p₁, p₂) + p₁′ = adjointtensorindices(t, p₂) + p₂′ = adjointtensorindices(t, p₁) + return has_shared_permute(t', (p₁′, p₂′)) end +# Braid """ - permute!(tdst::AbstractTensorMap{S, N₁, N₂}, tsrc::AbstractTensorMap{S}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=() - ) -> tdst + braid!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, + (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}, levels::Tuple) where {S,N₁,N₂} + -> tdst -Permute the indices of `tsrc` and write the result into `tdst`, with indices in `p1` playing -the role of the codomain or range of the map `tdst`, and indices in `p2` indicating the -domain of `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. -For more details see [`add!`](@ref), of which this is a special case. +See [`braid`](@ref) for creating a new tensor and [`add_braid!`](@ref) for a more general version. """ -@propagate_inbounds Base.permute!(tdst::AbstractTensorMap{S, N₁, N₂}, +@propagate_inbounds function braid!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}=()) where {S, N₁, N₂} = - add_permute!(true, tsrc, false, tdst, p1, p2) + (p₁, p₂)::Index2Tuple{N₁,N₂}, + levels::IndexTuple) where {S,N₁,N₂} + return add_braid!(tdst, tsrc, (p₁, p₂), levels, true, false) +end -# Braid -function braid(t::TensorMap{S}, levels::IndexTuple, - p1::IndexTuple, p2::IndexTuple=(); - copy::Bool = false) where {S} +""" + braid(tsrc::AbstractTensorMap{S}, (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}, levels::Tuple; + copy::Bool = false) where {S,N₁,N₂} + -> tdst::TensorMap{S,N₁,N₂} + +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!](@ref) and [`add_braid!`](@ref) +""" +function braid(t::TensorMap{S}, (p₁, p₂)::Index2Tuple, levels::IndexTuple; + copy::Bool=false) where {S} @assert length(levels) == numind(t) if BraidingStyle(sectortype(S)) isa SymmetricBraiding - return permute(t, p1, p2; copy = copy) + return permute(t, (p₁, p₂); copy=copy) end - if !copy && p1 == codomainind(t) && p2 == domainind(t) + if !copy && p₁ == codomainind(t) && p₂ == domainind(t) return t end # general case - cod = ProductSpace{S}(map(n->space(t, n), p1)) - dom = ProductSpace{S}(map(n->dual(space(t, n)), p2)) + cod = ProductSpace{S}(map(n -> space(t, n), p₁)) + dom = ProductSpace{S}(map(n -> dual(space(t, n)), p₂)) @inbounds begin - return add_braid!(true, t, false, similar(t, cod←dom), p1, p2, levels) + return braid!(similar(t, cod ← dom), t, levels, (p₁, p₂)) end end -@propagate_inbounds braid!(tdst::AbstractTensorMap{S, N₁, N₂}, - tsrc::AbstractTensorMap{S}, - levels::IndexTuple, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}=()) where {S, N₁, N₂} = - add!(true, tsrc, false, tdst, p1, p2, levels) +# TODO: braid for `AdjointTensorMap`; think about how to map the `levels` argument. # Transpose -LinearAlgebra.transpose!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, - p1::IndexTuple = reverse(domainind(tsrc)), - p2::IndexTuple = reverse(codomainind(tsrc))) = - add_transpose!(true, tsrc, false, tdst, p1, p2) +_transpose_indices(t::AbstractTensorMap) = (reverse(domainind(t)), reverse(codomainind(t))) +""" + transpose!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, + (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}) where {S,N₁,N₂} + -> 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`](@ref) for creating a new tensor and [`add_transpose!`](@ref) for a more general version. +""" +function LinearAlgebra.transpose!(tdst::AbstractTensorMap, + tsrc::AbstractTensorMap, + (p₁, p₂)::Index2Tuple=_transpose_indices(t)) + return add_transpose!(tdst, tsrc, (p₁, p₂), true, false) +end + +""" + transpose(tsrc::AbstractTensorMap{S}, (p₁, p₂)::Tuple{IndexTuple{N₁},IndexTuple{N₂}}; + copy::Bool=false) where {S,N₁,N₂} + -> tdst::TensorMap{S,N₁,N₂} + +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!](@ref) and [`add_permute!`](@ref) +""" function LinearAlgebra.transpose(t::TensorMap{S}, - p1::IndexTuple = reverse(domainind(t)), - p2::IndexTuple = reverse(codomainind(t)); - copy::Bool = false) where {S} + (p₁, p₂)::Index2Tuple=_transpose_indices(t); + copy::Bool=false) where {S} if sectortype(S) === Trivial - return permute(t, p1, p2; copy = copy) + return permute(t, (p₁, p₂); copy=copy) end - if !copy && p1 == codomainind(t) && p2 == domainind(t) + if !copy && p₁ == codomainind(t) && p₂ == domainind(t) return t end # general case - cod = ProductSpace{S}(map(n->space(t, n), p1)) - dom = ProductSpace{S}(map(n->dual(space(t, n)), p2)) + cod = ProductSpace{S}(map(n -> space(t, n), p₁)) + dom = ProductSpace{S}(map(n -> dual(space(t, n)), p₂)) @inbounds begin - return add_transpose!(true, t, false, similar(t, cod←dom), p1, p2) + return transpose!(similar(t, cod ← dom), t, (p₁, p₂)) end end function LinearAlgebra.transpose(t::AdjointTensorMap{S}, - p1::IndexTuple = reverse(domainind(t)), - p2::IndexTuple = reverse(codomainind(t)); - copy::Bool = false) where {S} - p1′ = map(n->adjointtensorindex(t, n), p2) - p2′ = map(n->adjointtensorindex(t, n), p1) - adjoint(transpose(adjoint(t), p1′, p2′; copy = copy)) + (p₁, p₂)::Index2Tuple=_transpose_indices(t); + copy::Bool=false) where {S} + p₁′ = map(n -> adjointtensorindex(t, n), p₂) + p₂′ = map(n -> adjointtensorindex(t, n), p₁) + return adjoint(transpose(adjoint(t), (p₁′, p₂′); copy=copy)) end # Twist -twist(t::AbstractTensorMap, i::Int; inv::Bool = false) = twist!(copy(t), i; inv = inv) +""" + twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) + -> t -function twist!(t::AbstractTensorMap, i::Int; inv::Bool = false) +Apply a twist to the `i`th index of `t`, storing the result in `t`. +If `inv=true`, use the inverse twist. + +See [`twist`](@ref) for creating a new tensor. +""" +function twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) if i > numind(t) msg = "Can't twist index $i of a tensor with only $(numind(t)) indices." throw(ArgumentError(msg)) @@ -142,11 +204,148 @@ function twist!(t::AbstractTensorMap, i::Int; inv::Bool = false) BraidingStyle(sectortype(t)) == Bosonic() && return t N₁ = numout(t) for (f₁, f₂) in fusiontrees(t) - θ = i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i-N₁]) + θ = i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i - N₁]) inv && (θ = θ') rmul!(t[f₁, f₂], θ) end return t end +""" + twist(t::AbstractTensorMap, i::Int; inv::Bool=false) + -> t + +Apply a twist to the `i`th index of `t` and return the result as a new tensor. +If `inv=true`, use the inverse twist. + +See [`twist!`](@ref) for storing the result in place. +""" +twist(t::AbstractTensorMap, i::Int; inv::Bool=false) = twist!(copy(t), i; inv=inv) + # Fusing and splitting +# TODO: add functionality for easy fusing and splitting of tensor indices + +#------------------------------------- +# Full implementations based on `add` +#------------------------------------- +@propagate_inbounds function add_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap, + p::Index2Tuple{N₁,N₂}, + α::Number, + β::Number, + backend::Backend...) where {S,N₁,N₂} + treepermuter(f₁, f₂) = permute(f₁, f₂, p[1], p[2]) + return add_transform!(tdst, tsrc, p, treepermuter, α, β, backend...) +end + +@propagate_inbounds function add_braid!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap, + p::Index2Tuple{N₁,N₂}, + levels::IndexTuple, + α::Number, + β::Number, + backend::Backend...) where {S,N₁,N₂} + length(levels) == numind(tsrc) || + throw(ArgumentError("incorrect levels $levels for tensor map $(codomain(tsrc)) ← $(domain(tsrc))")) + + levels1 = TupleTools.getindices(levels, codomainind(tsrc)) + levels2 = TupleTools.getindices(levels, domainind(tsrc)) + treebraider(f₁, f₂) = braid(f₁, f₂, p[1], levels1, levels2, p[2]) + return add_transform!(tdst, tsrc, p, treebraider, α, β, backend...) +end + +@propagate_inbounds function add_transpose!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap, + p::Index2Tuple{N₁,N₂}, + α::Number, + β::Number, + backend::Backend...) where {S,N₁,N₂} + treetransposer(f₁, f₂) = transpose(f₁, f₂, p[1], p[2]) + return add_transform!(tdst, tsrc, p, treetransposer, α, β, backend...) +end + +function add_transform!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap, + (p₁, p₂)::Index2Tuple{N₁,N₂}, + fusiontreetransform, + α::Number, + β::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₂) || + throw(SpaceMismatch("source = $(codomain(tsrc))←$(domain(tsrc)), + dest = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) + end + + I = sectortype(S) + if p₁ == codomainind(tsrc) && p₂ == domainind(tsrc) + add!(tdst, tsrc, α, β) + elseif I === Trivial + _add_trivial_kernel!(tdst, tsrc, (p₁, p₂), fusiontreetransform, α, β, backend...) + elseif FusionStyle(I) isa UniqueFusion + _add_abelian_kernel!(tdst, tsrc, (p₁, p₂), fusiontreetransform, α, β, backend...) + else + _add_general_kernel!(tdst, tsrc, (p₁, p₂), fusiontreetransform, α, β, backend...) + end + return tdst +end + +# internal methods: no argument types +function _add_trivial_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backend...) + TO.tensoradd!(tdst[], p, tsrc[], :N, α, β, backend...) + return nothing +end + +function _add_abelian_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backend...) + if Threads.nthreads() > 1 + Threads.@sync for (f₁, f₂) in fusiontrees(tsrc) + Threads.@spawn _add_abelian_block!(tdst, tsrc, p, fusiontreetransform, + f₁, f₂, α, β, backend...) + end + else + for (f₁, f₂) in fusiontrees(tsrc) + _add_abelian_block!(tdst, tsrc, p, fusiontreetransform, + f₁, f₂, α, β, backend...) + end + end + return tdst +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...) + return nothing +end + +function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backend...) + if iszero(β) + tdst = zerovector!(tdst) + elseif β != 1 + tdst = scale!(tdst, β) + end + if Threads.nthreads() > 1 + Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) + _add_sector!(tdst, tsrc, fusiontreemap, s₁, s₂, α, β, backend...) + end + 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...) + end + end + end + return nothing +end + +function _add_sectors!(tdst, tsrc, p, fusiontreetransform, s₁, s₂, α, β, backend...) + for (f₁, f₂) in fusiontrees(tsrc) + (f₁.outgoing == s₁ && f₂.outgoing == s₂) || continue + for ((f₁′, f₂′), coeff) in fusiontreetransform(f₁, f₂) + TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, true, backend...) + end + end + return nothing +end \ No newline at end of file diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index bd8a4cfa..7f52a8eb 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -22,7 +22,10 @@ end TO.tensorscalar(t::AbstractTensorMap) = scalar(t) -_canonicalize(p::Index2Tuple{N₁,N₂}, ::AbstractTensorMap{<:IndexSpace,N₁,N₂}) where {N₁,N₂} = p +function _canonicalize(p::Index2Tuple{N₁,N₂}, + ::AbstractTensorMap{<:IndexSpace,N₁,N₂}) where {N₁,N₂} + return p +end function _canonicalize(p::Index2Tuple, ::AbstractTensorMap) p′ = linearize(p) p₁ = TupleTools.getindices(p′, codomainind(t)) @@ -43,9 +46,7 @@ function TO.tensoradd!(C::AbstractTensorMap{S}, pC::Index2Tuple, else throw(ArgumentError("unknown conjugation flag $conjA")) end - # TODO: novel syntax for tensoradd!? - # tensoradd!(C, pC′, A′, α, β, backend...) - add!(α, A′, β, C, pC′[1], pC′[2]) + add_permute!(C, A′, pC′, α, β, backend...) return C end @@ -67,32 +68,32 @@ function TO.tensoradd_structure(pC::Index2Tuple{N₁,N₂}, end # tensortrace! -function TO.tensortrace!(C::AbstractTensorMap{S}, pC::Index2Tuple, - A::AbstractTensorMap{S}, qA::Index2Tuple, conjA::Symbol, +function TO.tensortrace!(C::AbstractTensorMap{S}, p::Index2Tuple, + A::AbstractTensorMap{S}, q::Index2Tuple, conjA::Symbol, α::Number, β::Number, backend::Backend...) where {S} if conjA == :N A′ = A - pC′ = _canonicalize(pC, C) - qA′ = qA + p′ = _canonicalize(p, C) + q′ = q elseif conjA == :C A′ = adjoint(A) - pC′ = adjointtensorindices(A, _canonicalize(pC, C)) - qA′ = adjointtensorindices(A, qA) + p′ = adjointtensorindices(A, _canonicalize(p, C)) + q′ = adjointtensorindices(A, q) else throw(ArgumentError("unknown conjugation flag $conjA")) end # TODO: novel syntax for tensortrace? # tensortrace!(C, pC′, A′, qA′, α, β, backend...) - trace!(α, A′, β, C, pC′[1], pC′[2], qA′[1], qA′[2]) + trace!(C, A′, p′, q′, α, β, backend...) return C end # tensorcontract! -function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple, +function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, pAB::Index2Tuple, A::AbstractTensorMap{S}, pA::Index2Tuple, conjA::Symbol, B::AbstractTensorMap{S}, pB::Index2Tuple, conjB::Symbol, α::Number, β::Number, backend::Backend...) where {S,N₁,N₂} - pC′ = _canonicalize(pC, C) + pAB′ = _canonicalize(pAB, C) if conjA == :N A′ = A pA′ = pA @@ -111,9 +112,7 @@ function TO.tensorcontract!(C::AbstractTensorMap{S,N₁,N₂}, pC::Index2Tuple, else throw(ArgumentError("unknown conjugation flag $conjB")) end - # TODO: novel syntax for tensorcontract? - # tensorcontract!(C, pC′, A′, pA′, B′, pB′, α, β, backend...) - contract!(α, A′, B′, β, C, pA′[1], pA′[2], pB′[2], pB′[1], pC′[1], pC′[2]) + contract!(C, A′, pA′, B′, pB′, pAB′, α, β, backend...) return C end @@ -127,8 +126,8 @@ end function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, pA::Index2Tuple, conjA, - B::AbstractTensorMap{S}, pB::Index2Tuple, conjB) where {S,N₁,N₂} - + 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...) @@ -137,189 +136,38 @@ function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, return dom → cod end -# Actual implementations -function cached_permute(sym::Symbol, t::TensorMap{S}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}=(); - copy::Bool=false) where {S,N₁,N₂} - cod = ProductSpace{S,N₁}(map(n -> space(t, n), p1)) - dom = ProductSpace{S,N₂}(map(n -> dual(space(t, n)), p2)) - # share data if possible - if !copy - if p1 === codomainind(t) && p2 === domainind(t) - return t - elseif has_shared_permute(t, p1, p2) - return TensorMap(reshape(t.data, dim(cod), dim(dom)), cod, dom) - end - end - # general case - @inbounds begin - tp = TO.cached_similar_from_indices(sym, scalartype(t), p1, p2, t, :N) - return add!(true, t, false, tp, p1, p2) - end -end - -function cached_permute(sym::Symbol, t::AdjointTensorMap, - p1::IndexTuple, p2::IndexTuple=(); - copy::Bool=false) - p1′ = adjointtensorindices(t, p2) - p2′ = adjointtensorindices(t, p1) - return adjoint(cached_permute(sym, adjoint(t), p1′, p2′; copy=copy)) -end - -@propagate_inbounds function add!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S}, - p1::IndexTuple, p2::IndexTuple) where {S} - I = sectortype(S) - if BraidingStyle(I) isa SymmetricBraiding - add_permute!(α, tsrc, β, tdst, p1, p2) - else - throw(ArgumentError("add! without levels is defined only if `BraidingStyle(sectortype(...)) isa SymmetricBraiding`")) - end -end -@propagate_inbounds function add!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S}, - p1::IndexTuple, p2::IndexTuple, - levels::IndexTuple) where {S} - return add_braid!(α, tsrc, β, tdst, p1, p2, levels) -end - -@propagate_inbounds function add_permute!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S,N₁,N₂}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}) where {S,N₁,N₂} - return _add!(α, tsrc, β, tdst, p1, p2, (f₁, f₂) -> permute(f₁, f₂, p1, p2)) -end -@propagate_inbounds function add_braid!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S,N₁,N₂}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}, - levels::IndexTuple) where {S,N₁,N₂} - length(levels) == numind(tsrc) || - throw(ArgumentError("incorrect levels $levels for tensor map $(codomain(tsrc)) ← $(domain(tsrc))")) - - levels1 = TupleTools.getindices(levels, codomainind(tsrc)) - levels2 = TupleTools.getindices(levels, domainind(tsrc)) - return _add!(α, tsrc, β, tdst, p1, p2, - (f₁, f₂) -> braid(f₁, f₂, levels1, levels2, p1, p2)) -end -@propagate_inbounds function add_transpose!(α, tsrc::AbstractTensorMap{S}, - β, tdst::AbstractTensorMap{S,N₁,N₂}, - p1::IndexTuple{N₁}, - p2::IndexTuple{N₂}) where {S,N₁,N₂} - return _add!(α, tsrc, β, tdst, p1, p2, (f₁, f₂) -> transpose(f₁, f₂, p1, p2)) -end - -function _add!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, fusiontreemap) where {S,N₁,N₂} - @boundscheck begin - all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || - throw(SpaceMismatch("tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || - throw(SpaceMismatch("tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - end - - # do some kind of dispatch which is compiled away if S is known at compile time, - # and makes the compiler give up quickly if S is unknown - I = sectortype(S) - i = I === Trivial ? 1 : (FusionStyle(I) isa UniqueFusion ? 2 : 3) - if p1 == codomainind(tsrc) && p2 == domainind(tsrc) - axpby!(α, tsrc, β, tdst) - else - _add_kernel! = _add_kernels[i] - _add_kernel!(α, tsrc, β, tdst, p1, p2, fusiontreemap) - end - return tdst -end - -function _add_trivial_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) - cod = codomain(tsrc) - dom = domain(tsrc) - n = length(cod) - pdata = (p1..., p2...) - axpby!(α, permutedims(tsrc[], pdata), β, tdst[]) - return nothing -end - -function _add_abelian_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) - if Threads.nthreads() > 1 - nstridedthreads = Strided.get_num_threads() - Strided.set_num_threads(1) - Threads.@sync for (f₁, f₂) in fusiontrees(tsrc) - Threads.@spawn _addabelianblock!(α, tsrc, β, tdst, p1, p2, f₁, f₂, - fusiontreemap) - end - Strided.set_num_threads(nstridedthreads) - else # debugging is easier this way - for (f₁, f₂) in fusiontrees(tsrc) - _addabelianblock!(α, tsrc, β, tdst, p1, p2, f₁, f₂, fusiontreemap) - end - end - return nothing -end - -function _addabelianblock!(α, tsrc::AbstractTensorMap, - β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, - f₁::FusionTree, f₂::FusionTree, - fusiontreemap) - cod = codomain(tsrc) - dom = domain(tsrc) - (f₁′, f₂′), coeff = first(fusiontreemap(f₁, f₂)) - pdata = (p1..., p2...) - @inbounds axpby!(α * coeff, permutedims(tsrc[f₁, f₂], pdata), β, tdst[f₁′, f₂′]) -end - -function _add_general_kernel!(α, tsrc::AbstractTensorMap, β, tdst::AbstractTensorMap, - p1::IndexTuple, p2::IndexTuple, fusiontreemap) - cod = codomain(tsrc) - dom = domain(tsrc) - n = length(cod) - pdata = (p1..., p2...) - if iszero(β) - fill!(tdst, β) - elseif β != 1 - mul!(tdst, β, tdst) - end - for (f₁, f₂) in fusiontrees(tsrc) - for ((f₁′, f₂′), coeff) in fusiontreemap(f₁, f₂) - @inbounds axpy!(α * coeff, permutedims(tsrc[f₁, f₂], pdata), tdst[f₁′, f₂′]) - end - end - return nothing -end +#---------------- +# IMPLEMENTATONS +#---------------- -const _add_kernels = (_add_trivial_kernel!, _add_abelian_kernel!, _add_general_kernel!) - -function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N₁,N₂}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S,N₁,N₂,N₃} +# Trace implementation +#---------------------- +function trace!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, + (p₁, p₂)::Index2Tuple{N₁,N₂}, (q₁, q₂)::Index2Tuple{N₃,N₃}, + backend...) where {S,N₁,N₂,N₃} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @boundscheck begin - all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || + all(i -> space(tsrc, p₁[i]) == space(tdst, i), 1:N₁) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || + tdst = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) + all(i -> space(tsrc, p₂[i]) == space(tdst, N₁ + i), 1:N₂) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) - all(i -> space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || + tdst = $(codomain(tdst))←$(domain(tdst)), p₁ = $(p₁), p₂ = $(p₂)")) + all(i -> space(tsrc, q₁[i]) == dual(space(tsrc, q₂[i])), 1:N₃) || throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), - q1 = $(q1), q2 = $(q2)")) + q₁ = $(q₁), q₂ = $(q₂)")) end I = sectortype(S) + # TODO: is it worth treating UniqueFusion separately? Is it worth to add multithreading support? if I === Trivial cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) - TO.tensortrace!(tdst[], (p1, p2), tsrc[], (q1, q2), :N, α, β) + TO.tensortrace!(tdst[], (p₁, p₂), tsrc[], (q₁, q₂), :N, α, β) # elseif FusionStyle(I) isa UniqueFusion - # TODO: is it worth multithreading UniqueFusion case for traces? else cod = codomain(tsrc) dom = domain(tsrc) @@ -329,40 +177,43 @@ function trace!(α, tsrc::AbstractTensorMap{S}, β, tdst::AbstractTensorMap{S,N elseif β != 1 mul!(tdst, β, tdst) end - r1 = (p1..., q1...) - r2 = (p2..., q2...) + r₁ = (p₁..., q₁...) + r₂ = (p₂..., q₂...) for (f₁, f₂) in fusiontrees(tsrc) - for ((f₁′, f₂′), coeff) in permute(f₁, f₂, r1, r2) - f₁′′, g1 = split(f₁′, N₁) - f₂′′, g2 = split(f₂′, N₂) - if g1 == g2 - coeff *= dim(g1.coupled) / dim(g1.uncoupled[1]) - for i in 2:length(g1.uncoupled) - if !(g1.isdual[i]) - coeff *= twist(g1.uncoupled[i]) - end + for ((f₁′, f₂′), coeff) in permute(f₁, f₂, r₁, r₂) + f₁′′, g₁ = split(f₁′, N₁) + f₂′′, g₂ = split(f₂′, N₂) + g₁ == g₂ || continue + coeff *= dim(g₁.coupled) / dim(g₁.uncoupled[1]) + for i in 2:length(g₁.uncoupled) + if !(g₁.isdual[i]) + coeff *= twist(g₁.uncoupled[i]) end - TO.tensortrace!(tdst[f₁′′, f₂′′], (p1, p2), tsrc[f₁, f₂], (q1, q2), :N, - α * coeff, true) end + C = tdst[f₁′′, f₂′′] + A = tsrc[f₁, f₂] + α′ = α * coeff + TO.tensortrace!(C, (p₁, p₂), A, (q₁, q₂), :N, α′, true, backend...) end end end return tdst end +# Contract implementation +#------------------------- # TODO: contraction with either A or B a rank (1, 1) tensor does not require to # permute the fusion tree and should therefore be special cased. This will speed # up MPS algorithms -function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple{N₁}, cindA::IndexTuple, - oindB::IndexTuple{N₂}, cindB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S,N₁,N₂} +function contract!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, pA::Index2Tuple, + B::AbstractTensorMap{S}, pB::Index2Tuple, + pAB::Index2Tuple{N₁,N₂}, + α::Number, β::Number, backend...) where {S,N₁,N₂} + # find optimal contraction scheme hsp = has_shared_permute - ipC = TupleTools.invperm((p1..., p2...)) + ipC = TupleTools.invperm(linearize(pAB)) oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) @@ -392,13 +243,13 @@ function contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, if min(memcost1, memcost2) <= min(memcost3, memcost4) if memcost1 <= memcost2 - return _contract!(α, A, B, β, C, oindA, cindA′, oindB, cindB′, p1, p2, syms) + return _contract!(α, A, B, β, C, oindA, cindA′, oindB, cindB′, p₁, p₂, syms) else - return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p1, p2, syms) + return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p₁, p₂, syms) end else - p1′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p1) - p2′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p2) + p1′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p₁) + p2′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p₂) if memcost3 <= memcost4 return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′, syms) else @@ -411,7 +262,7 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, β, C::AbstractTensorMap{S}, oindA::IndexTuple{N₁}, cindA::IndexTuple, oindB::IndexTuple{N₂}, cindB::IndexTuple, - p1::IndexTuple, p2::IndexTuple, + p₁::IndexTuple, p₂::IndexTuple, syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S,N₁,N₂} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) @@ -424,13 +275,8 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, end end end - if syms === nothing - A′ = permute(A, oindA, cindA; copy=copyA) - B′ = permute(B, cindB, oindB) - else - A′ = cached_permute(syms[1], A, oindA, cindA; copy=copyA) - B′ = cached_permute(syms[2], B, cindB, oindB) - end + A′ = permute(A, oindA, cindA; copy=copyA) + B′ = permute(B, cindB, oindB) if BraidingStyle(sectortype(S)) isa Fermionic for i in domainind(A′) if !isdual(space(A′, i)) @@ -438,28 +284,21 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, end end end - ipC = TupleTools.invperm((p1..., p2...)) + ipC = TupleTools.invperm((p₁..., p₂...)) oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) if has_shared_permute(C, oindAinC, oindBinC) C′ = permute(C, oindAinC, oindBinC) mul!(C′, A′, B′, α, β) else - if syms === nothing - C′ = A′ * B′ - else - p1′ = ntuple(identity, N₁) - p2′ = N₁ .+ ntuple(identity, N₂) - TC = scalartype(C) - C′ = TO.cached_similar_from_indices(syms[3], TC, oindA, oindB, p1′, p2′, A, B, - :N, :N) - mul!(C′, A′, B′) - end - add!(α, C′, β, C, p1, p2) + C′ = A′ * B′ + add!(α, C′, β, C, p₁, p₂) end return C end +# Scalar implementation +#----------------------- function scalar(t::AbstractTensorMap{S}) where {S<:IndexSpace} return dim(codomain(t)) == dim(domain(t)) == 1 ? first(blocks(t))[2][1, 1] : throw(DimensionMismatch()) From 951d3412a05f3c4b38f0194e123eac3f378ac67c Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:27:39 +0200 Subject: [PATCH 29/57] Testsuite `@planar` (#76) * Add Trivial anyon * SpaceMismatches print more information * Add `checkcontractible` for TensorMaps * Add planar tests * Add `tensorcost` --- src/sectors/anyons.jl | 32 ++++++++ src/spaces/planarspace.jl | 11 +++ src/spaces/vectorspaces.jl | 1 + src/tensors/linalg.jl | 2 +- src/tensors/tensoroperations.jl | 11 +++ src/tensors/vectorinterface.jl | 2 +- test/planar.jl | 133 ++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 8 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/spaces/planarspace.jl create mode 100644 test/planar.jl diff --git a/src/sectors/anyons.jl b/src/sectors/anyons.jl index 187049b3..f431aea4 100644 --- a/src/sectors/anyons.jl +++ b/src/sectors/anyons.jl @@ -1,3 +1,35 @@ +# PlanarTrivial +""" + struct PlanarTrivial <: Sector + PlanarTrivial() + +Represents a trivial anyon sector, i.e. a trivial sector without braiding. This is mostly +useful for testing. +""" +struct PlanarTrivial <: Sector end + +Base.IteratorSize(::Type{SectorValues{PlanarTrivial}}) = HasLength() +Base.length(::SectorValues{PlanarTrivial}) = 1 +Base.iterate(::SectorValues{PlanarTrivial}, i = 0) = i == 0 ? (PlanarTrivial(), 1) : nothing +function Base.getindex(::SectorValues{PlanarTrivial}, i::Int) + return i == 1 ? PlanarTrivial() : + throw(BoundsError(values(PlanarTrivial), i)) +end +findindex(::SectorValues{PlanarTrivial}, c::PlanarTrivial) = 1 +Base.isless(::PlanarTrivial, ::PlanarTrivial) = false + +Base.one(::Type{PlanarTrivial}) = PlanarTrivial() +Base.conj(::PlanarTrivial) = PlanarTrivial() + +FusionStyle(::Type{PlanarTrivial}) = UniqueFusion() +BraidingStyle(::Type{PlanarTrivial}) = NoBraiding() +Base.isreal(::Type{PlanarTrivial}) = true + +Nsymbol(::Vararg{PlanarTrivial,3}) = 1 +Fsymbol(::Vararg{PlanarTrivial,6}) = 1 + +⊗(::PlanarTrivial, ::PlanarTrivial) = (PlanarTrivial(),) + # FibonacciAnyons """ struct FibonacciAnyon <: Sector diff --git a/src/spaces/planarspace.jl b/src/spaces/planarspace.jl new file mode 100644 index 00000000..e5122124 --- /dev/null +++ b/src/spaces/planarspace.jl @@ -0,0 +1,11 @@ +struct PlanarNumbers end # this is probably not a field? +const ℙ = PlanarNumbers() +Base.show(io::IO, ::PlanarNumbers) = print(io, "ℙ") + +# convenience constructor +Base.:^(::PlanarNumbers, d::Int) = Vect[PlanarTrivial](PlanarTrivial() => d) + +# convenience show +function Base.show(io::IO, V::GradedSpace{PlanarTrivial}) + return print(io, isdual(V) ? "(ℙ^$(dim(V)))'" : "ℙ^$(dim(V))") +end \ No newline at end of file diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 9c26a4cd..e1c6e05a 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -255,6 +255,7 @@ include("generalspace.jl") # space with internal structure corresponding to the irreducible representations of # a group, or more generally, the simple objects of a fusion category. include("gradedspace.jl") +include("planarspace.jl") # Specific realizations of CompositeSpace types #----------------------------------------------- diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 9368a241..047f5b00 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -233,7 +233,7 @@ function LinearAlgebra.mul!(tC::AbstractTensorMap, tA::AbstractTensorMap, tB::AbstractTensorMap, α = true, β = false) if !(codomain(tC) == codomain(tA) && domain(tC) == domain(tB) && domain(tA) == codomain(tB)) - throw(SpaceMismatch()) + 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 diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 7f52a8eb..679650a6 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -136,6 +136,17 @@ function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, return dom → cod end +function TO.checkcontractible(tA::AbstractTensorMap{S}, iA::Int, conjA::Symbol, + tB::AbstractTensorMap{S}, iB::Int, conjB::Symbol, label) where {S} + sA = TO.tensorstructure(tA, iA, conjA)' + sB = TO.tensorstructure(tB, iB, conjB) + sA == sB || + throw(SpaceMismatch("incompatible spaces for $label: $sA ≠ $sB")) + return nothing +end + +TO.tensorcost(t::AbstractTensorMap, i::Int) = dim(space(t, i)) + #---------------- # IMPLEMENTATONS #---------------- diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index 141d2479..9562aea8 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -62,7 +62,7 @@ function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::O return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) - space(ty) == space(tx) || throw(SpaceMismatch()) + space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) VectorInterface.add!(block(ty, c), block(tx, c), α, β) end diff --git a/test/planar.jl b/test/planar.jl new file mode 100644 index 00000000..c026a334 --- /dev/null +++ b/test/planar.jl @@ -0,0 +1,133 @@ +using TensorKit, TensorOperations, Test +using TensorKit: planaradd!, planartrace!, planarcontract! +using TensorKit: PlanarTrivial, ℙ + +""" + force_planar(obj) + +Replace an object with a planar equivalent -- i.e. one that disallows braiding. +""" +force_planar(V::ComplexSpace) = isdual(V) ? (ℙ^dim(V))' : ℙ^dim(V) +function force_planar(V::GradedSpace) + return GradedSpace((c ⊠ PlanarTrivial() => dim(V, c) for c in sectors(V))..., isdual(V)) +end +force_planar(V::ProductSpace) = mapreduce(force_planar, ⊗, V) +function force_planar(tsrc::TensorMap{ComplexSpace}) + tdst = TensorMap(undef, eltype(tsrc), + force_planar(codomain(tsrc)) ← force_planar(domain(tsrc))) + copyto!(blocks(tdst)[PlanarTrivial()], blocks(tsrc)[Trivial()]) + return tdst +end +function force_planar(tsrc::TensorMap{<:GradedSpace}) + tdst = TensorMap(undef, eltype(tsrc), + force_planar(codomain(tsrc)) ← force_planar(domain(tsrc))) + for (c, b) in blocks(tsrc) + copyto!(blocks(tdst)[c ⊠ PlanarTrivial()], b) + end + return tdst +end + +@testset "planar methods" verbose = true begin + @testset "planaradd" begin + A = TensorMap(randn, ℂ^2 ⊗ ℂ^3 ← ℂ^6 ⊗ ℂ^5 ⊗ ℂ^4) + C = TensorMap(randn, (ℂ^5)' ⊗ (ℂ^6)' ← ℂ^4 ⊗ (ℂ^3)' ⊗ (ℂ^2)') + A′ = force_planar(A) + C′ = force_planar(C) + pC = ((4, 3), (5, 2, 1)) + + @test force_planar(tensoradd!(C, pC, A, :N, true, true)) ≈ + planaradd!(C′, pC, A′, true, true) + end + + @testset "planartrace" begin + A = TensorMap(randn, ℂ^2 ⊗ ℂ^3 ← ℂ^2 ⊗ ℂ^5 ⊗ ℂ^4) + C = TensorMap(randn, (ℂ^5)' ⊗ ℂ^3 ← ℂ^4) + A′ = force_planar(A) + C′ = force_planar(C) + pA = ((1,), (3,)) + pC = ((4, 2), (5,)) + + @test force_planar(tensortrace!(C, pC, A, pA, :N, true, true)) ≈ + planartrace!(C′, pC, A′, pA, true, true) + end + + @testset "planarcontract" begin + A = TensorMap(randn, ℂ^2 ⊗ ℂ^3 ← ℂ^2 ⊗ ℂ^5 ⊗ ℂ^4) + B = TensorMap(randn, ℂ^2 ⊗ ℂ^4 ← ℂ^4 ⊗ ℂ^3) + C = TensorMap(randn, (ℂ^5)' ⊗ (ℂ^2)' ⊗ ℂ^2 ← (ℂ^2)' ⊗ ℂ^4) + + A′ = force_planar(A) + B′ = force_planar(B) + C′ = force_planar(C) + + pA = ((1, 3, 4), (5, 2)) + pB = ((2, 4), (1, 3)) + pC = ((3, 2, 1), (4, 5)) + + @test force_planar(tensorcontract!(C, pC, A, pA, :N, B, pB, :N, true, true)) ≈ + planarcontract!(C′, pC, A′, pA, B′, pB, true, true) + end +end + +@testset "@planar" verbose=true begin + T = ComplexF64 + @testset "MPS networks" begin + P = ℂ^2 + Vmps = ℂ^12 + Vmpo = ℂ^4 + + # ∂AC + # ------- + x = TensorMap(randn, T, Vmps ⊗ P ← Vmps) + O = TensorMap(randn, T, Vmpo ⊗ P ← P ⊗ Vmpo) + GL = TensorMap(randn, T, Vmps ⊗ Vmpo' ← Vmps) + GR = TensorMap(randn, T, Vmps ⊗ Vmpo ← Vmps) + + x′ = force_planar(x) + O′ = force_planar(O) + GL′ = force_planar(GL) + GR′ = force_planar(GR) + + @tensor y[-1 -2; -3] := GL[-1 2; 1] * x[1 3; 4] * O[2 -2; 3 5] * GR[4 5; -3] + @planar y′[-1 -2; -3] := GL′[-1 2; 1] * x′[1 3; 4] * O′[2 -2; 3 5] * GR′[4 5; -3] + @test force_planar(y) ≈ y′ + + # ∂AC2 + # ------- + x2 = TensorMap(randn, T, Vmps ⊗ P ← Vmps ⊗ P') + x2′ = force_planar(x2) + @tensor contractcheck=true y2[-1 -2; -3 -4] := GL[-1 7; 6] * x2[6 5; 1 3] * O[7 -2; 5 4] * O[4 -4; 3 2] * GR[1 2; -3] + @planar y2′[-1 -2; -3 -4] := GL′[-1 7; 6] * x2′[6 5; 1 3] * O′[7 -2; 5 4] * O′[4 -4; 3 2] * GR′[1 2; -3] + @test force_planar(y2) ≈ y2′ + end + + @testset "MERA networks" begin + Vmera = ℂ^2 + + u = TensorMap(randn, T, Vmera ⊗ Vmera ← Vmera ⊗ Vmera) + w = TensorMap(randn, T, Vmera ⊗ Vmera ← Vmera) + ρ = TensorMap(randn, T, Vmera ⊗ Vmera ⊗ Vmera ← Vmera ⊗ Vmera ⊗ Vmera) + h = TensorMap(randn, T, Vmera ⊗ Vmera ⊗ Vmera ← Vmera ⊗ Vmera ⊗ Vmera) + + u′ = force_planar(u) + w′ = force_planar(w) + ρ′ = force_planar(ρ) + h′ = force_planar(h) + + @tensor begin + C = (((((((h[9 3 4; 5 1 2] * u[1 2; 7 12]) * conj(u[3 4; 11 13])) * + (u[8 5; 15 6] * w[6 7; 19])) * + (conj(u[8 9; 17 10]) * conj(w[10 11; 22]))) * + ((w[12 14; 20] * conj(w[13 14; 23])) * ρ[18 19 20; 21 22 23])) * + w[16 15; 18]) * conj(w[16 17; 21])) + end + @planar begin + C′ = (((((((h′[9 3 4; 5 1 2] * u′[1 2; 7 12]) * conj(u′[3 4; 11 13])) * + (u′[8 5; 15 6] * w′[6 7; 19])) * + (conj(u′[8 9; 17 10]) * conj(w′[10 11; 22]))) * + ((w′[12 14; 20] * conj(w′[13 14; 23])) * ρ′[18 19 20; 21 22 23])) * + w′[16 15; 18]) * conj(w′[16 17; 21])) + end + @test C ≈ C′ + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 8492ec1c..bd48441a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,6 +61,7 @@ include("sectors.jl") include("fusiontrees.jl") include("spaces.jl") include("tensors.jl") +include("planar.jl") Tf = time() printstyled("Finished all tests in ", string(round((Tf - Ti) / 60; sigdigits=3)), From 4877ff4c7ff8e1b90bdcb565964c85756dc489c7 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 10 Aug 2023 17:46:59 +0200 Subject: [PATCH 30/57] Add planar test with braiding tensor + conj --- test/planar.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/planar.jl b/test/planar.jl index c026a334..8610f48b 100644 --- a/test/planar.jl +++ b/test/planar.jl @@ -99,6 +99,21 @@ end @tensor contractcheck=true y2[-1 -2; -3 -4] := GL[-1 7; 6] * x2[6 5; 1 3] * O[7 -2; 5 4] * O[4 -4; 3 2] * GR[1 2; -3] @planar y2′[-1 -2; -3 -4] := GL′[-1 7; 6] * x2′[6 5; 1 3] * O′[7 -2; 5 4] * O′[4 -4; 3 2] * GR′[1 2; -3] @test force_planar(y2) ≈ y2′ + + # transfer matrix + # ---------------- + v = TensorMap(randn, T, Vmps ← Vmps) + v′ = force_planar(v) + @tensor ρ[-1; -2] := x[-1 2; 1] * conj(x[-2 2; 3]) * v[1; 3] + @planar ρ′[-1; -2] := x′[-1 2; 1] * conj(x′[-2 2; 3]) * v′[1; 3] + @test force_planar(ρ) ≈ ρ′ + + @tensor ρ2[-1 -2; -3] := GL[1 -2; 3] * x[3 2; -3] * conj(x[1 2; -1]) + @plansor ρ3[-1 -2; -3] := GL[1 2; 4] * x[4 5; -3] * τ[2 3; 5 -2] * conj(x[1 3; -1]) + @planar ρ2′[-1 -2; -3] := GL′[1 2; 4] * x′[4 5; -3] * τ[2 3; 5 -2] * + conj(x′[1 3; -1]) + @test force_planar(ρ2) ≈ ρ2′ + @test ρ2 ≈ ρ3 end @testset "MERA networks" begin From 1166f2497815dc8038d4e7c170466dd223cb87b9 Mon Sep 17 00:00:00 2001 From: Jutho Date: Fri, 11 Aug 2023 15:34:56 +0200 Subject: [PATCH 31/57] part2 - still wip --- src/auxiliary/deprecate.jl | 2 +- src/planar/planaroperations.jl | 1 + src/planar/postprocessors.jl | 16 +++-- src/tensors/braidingtensor.jl | 105 +++++++++++++++++------------- src/tensors/factorizations.jl | 34 +++++----- src/tensors/indexmanipulations.jl | 2 +- src/tensors/tensoroperations.jl | 6 +- 7 files changed, 94 insertions(+), 72 deletions(-) diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index 3faf19c4..37569f53 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -2,6 +2,6 @@ import Base: eltype, transpose @deprecate eltype(T::Type{<:AbstractTensorMap}) scalartype(T) @deprecate eltype(t::AbstractTensorMap) scalartype(t) -@deprecate permute(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) permutedims(t, (p1, p2); copy=copy) +@deprecate permute(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) permute(t, (p1, p2); copy=copy) @deprecate transpose(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) transpose(t, (p1, p2); copy=copy) @deprecate braid(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple, levels; copy::Bool=false) braid(t, (p1, p2), levels; copy=copy) diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index 3349545f..45d7e969 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -38,6 +38,7 @@ function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N return C end + function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, pAB::Index2Tuple{N₁,N₂}, A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, pB::Index2Tuple, α, β, backend::Backend...) where {S,N₁,N₂} diff --git a/src/planar/postprocessors.jl b/src/planar/postprocessors.jl index 63f234b7..1daa1836 100644 --- a/src/planar/postprocessors.jl +++ b/src/planar/postprocessors.jl @@ -49,23 +49,31 @@ end # Replace the tensor operations created by `TO.instantiate` with the corresponding # planar operations, immediately inserting them with `GlobalRef`. + +# NOTE: work around a somewhat unfortunate interface choice in TensorOperations, which we will correct in the future. +_planaradd!(C, p, A, α, β, backend...) = planaradd!(C, A, p, α, β, backend...) +_planartrace!(C, p, A, q, α, β, backend...) = planartrace!(C, A, p, q, α, β, backend...) +_planarcontract!(C, pAB, A, pA, B, pB, α, β, backend...) = planarcontract!(C, A, pA, B, pB, pAB, α, β, backend...) +# TODO: replace _planarmethod with planarmethod in everything below +const _PLANAR_OPERATIONS = (:_planaradd!, :_planartrace!, :_planarcontract!) + function _insert_planar_operations(ex) if isexpr(ex, :call) if ex.args[1] == GlobalRef(TensorOperations, :tensoradd!) conjA = popat!(ex.args, 5) @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planaradd!)), + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:_planaradd!)), map(_insert_planar_operations, ex.args[2:end])...) elseif ex.args[1] == GlobalRef(TensorOperations, :tensorcontract!) conjB = popat!(ex.args, 9) conjA = popat!(ex.args, 6) @assert conjA == conjB == :(:N) "conj flag should be `:N` ($conjA), ($conjB)" - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planarcontract!)), + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:_planarcontract!)), map(_insert_planar_operations, ex.args[2:end])...) elseif ex.args[1] == GlobalRef(TensorOperations, :tensortrace!) conjA = popat!(ex.args, 6) @assert conjA == :(:N) "conj flag should be `:N` ($conjA)" - return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planartrace!)), + return Expr(ex.head, GlobalRef(TensorKit, Symbol(:_planartrace!)), map(_insert_planar_operations, ex.args[2:end])...) elseif ex.args[1] in TensorOperations.tensoroperationsfunctions return Expr(ex.head, GlobalRef(TensorOperations, ex.args[1]), @@ -77,8 +85,6 @@ function _insert_planar_operations(ex) return ex end -const _PLANAR_OPERATIONS = (:planaradd!, :planarcontract!, :planartrace!) - # Mimick `TO.insert_operationbackend` for planar operations. function insert_operationbackend(ex, backend) if isexpr(ex, :call) && ex.args[1] isa GlobalRef && diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 1e34ba9c..862769fe 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -11,7 +11,7 @@ struct BraidingTensor{S<:IndexSpace, A} <: AbstractTensorMap{S, 2, 2} V1::S V2::S adjoint::Bool - function BraidingTensor(V1::S, V2::S, adjoint::Bool = false, ::Type{A} = Matrix{ComplexF64}) where + function BraidingTensor{S,A}(V1::S, V2::S, adjoint::Bool = false) where {S<:IndexSpace, A<:DenseMatrix} for a in sectors(V1) for b in sectors(V2) @@ -25,6 +25,13 @@ struct BraidingTensor{S<:IndexSpace, A} <: AbstractTensorMap{S, 2, 2} # partial construction: only construct rowr and colr when needed end end +function BraidingTensor(V1::S, V2::S, adjoint::Bool = false) where {S<:IndexSpace} + if BraidingStyle(sectortype(S)) isa SymmetricBraiding + return BraidingTensor{S, Matrix{Float64}}(V1, V2, adjoint) + else + return BraidingTensor{S, Matrix{ComplexF64}}(V1, V2, adjoint) + end +end Base.adjoint(b::BraidingTensor{S,A}) where {S<:IndexSpace, A<:DenseMatrix} = BraidingTensor(b.V1, b.V2, !b.adjoint, A) @@ -168,12 +175,14 @@ end blocks(b::BraidingTensor) = blocks(TensorMap(b)) -function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple{2}, cindA::IndexTuple{2}, - oindB::IndexTuple, cindB::IndexTuple{2}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} +function planar_contract!(C::AbstractTensorMap{S}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{2,2}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{2,<:Any}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) @@ -214,12 +223,15 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, end return C end -function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple, cindA::IndexTuple{2}, - oindB::IndexTuple{2}, cindB::IndexTuple{2}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} +function planar_contract!(C::AbstractTensorMap{S}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{<:Any,2}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{2,2}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} + codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = @@ -259,13 +271,15 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, C end -function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple{0}, cindA::IndexTuple{4}, - oindB::IndexTuple, cindB::IndexTuple{4}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} - +function planar_contract!(C::AbstractTensorMap{S}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{0,4}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{4,<:Any}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} + codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = @@ -327,13 +341,15 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, return C end -function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple, cindA::IndexTuple{4}, - oindB::IndexTuple{0}, cindB::IndexTuple{4}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} - +function planar_contract!(C::AbstractTensorMap{S}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{0,4}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{4,<:Any}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} + codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = @@ -395,13 +411,14 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, return C end -function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple{1}, cindA::IndexTuple{3}, - oindB::IndexTuple, cindB::IndexTuple{3}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} - +function planar_contract!(C::AbstractTensorMap{S}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{1,3}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{1,<:Any}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = @@ -457,13 +474,15 @@ function planar_contract!(α, A::BraidingTensor{S}, B::AbstractTensorMap{S}, return C end -function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, - β, C::AbstractTensorMap{S}, - oindA::IndexTuple, cindA::IndexTuple{3}, - oindB::IndexTuple{1}, cindB::IndexTuple{3}, - p1::IndexTuple, p2::IndexTuple, - syms::Union{Nothing, NTuple{3, Symbol}}) where {S} - +function planar_contract!(C::AbstractTensorMap{S}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{<:Any,3}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{3,1}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} + codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = @@ -520,7 +539,3 @@ function planar_contract!(α, A::AbstractTensorMap{S}, B::BraidingTensor{S}, end has_shared_permute(t::BraidingTensor, args...) = false -function cached_permute(sym::Symbol, t::BraidingTensor, p1, p2; copy=false) - tp = TO.cached_similar_from_indices(sym, scalartype(t), p1, p2, t, :N) - return add!(true, t, false, tp, p1, p2) -end diff --git a/src/tensors/factorizations.jl b/src/tensors/factorizations.jl index 29c7970d..2b2d0f31 100644 --- a/src/tensors/factorizations.jl +++ b/src/tensors/factorizations.jl @@ -24,7 +24,7 @@ Base.@deprecate( -> 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. +`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 @@ -51,14 +51,14 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `tsvd(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ tsvd(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - tsvd!(permute(t, p1, p2; copy = true); kwargs...) + tsvd!(permute(t, (p1, p2); copy = true); kwargs...) """ leftorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R Create orthonormal basis `Q` for indices in `leftind`, and remainder `R` such that -`permute(t, leftind, rightind) = Q*R`. +`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` @@ -76,14 +76,14 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `InnerProductStyle(t) === EuclideanProduct()`. """ leftorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - leftorth!(permute(t, p1, p2; copy = true); kwargs...) + leftorth!(permute(t, (p1, p2); copy = true); kwargs...) """ rightorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q Create orthonormal basis `Q` for indices in `rightind`, and remainder `L` such that -`permute(t, leftind, rightind) = L*Q`. +`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` @@ -103,14 +103,14 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `InnerProductStyle(t) === EuclideanProduct()`. """ rightorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - rightorth!(permute(t, p1, p2; copy = true); kwargs...) + rightorth!(permute(t, (p1, p2); copy = true); kwargs...) """ leftnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; 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`. +`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` @@ -128,7 +128,7 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `InnerProductStyle(t) === EuclideanProduct()`. """ leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - leftnull!(permute(t, p1, p2; copy = true); kwargs...) + leftnull!(permute(t, (p1, p2); copy = true); kwargs...) """ rightnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; @@ -137,7 +137,7 @@ leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = 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`. +`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` @@ -155,7 +155,7 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `InnerProductStyle(t) === EuclideanProduct()`. """ rightnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - rightnull!(permute(t, p1, p2; copy = true); kwargs...) + rightnull!(permute(t, (p1, p2); copy = true); kwargs...) """ eigen(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V @@ -168,7 +168,7 @@ to be destroyed/overwritten, by using `eigen!(t)`. Note that the permuted tensor `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 +permute(t, (leftind, rightind)) * V = V * D ``` Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of dense @@ -177,7 +177,7 @@ matrices. See the corresponding documentation for more information. See also `eig` and `eigh` """ LinearAlgebra.eigen(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - eigen!(permute(t, p1, p2; copy = true); kwargs...) + eigen!(permute(t, (p1, p2); copy = true); kwargs...) """ eig(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V @@ -193,7 +193,7 @@ indices of `t` is used. In that case, less memory is allocated if one allows the 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 +permute(t, (leftind, rightind)) * V = V * D ``` Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of dense matrices. See the corresponding documentation for more information. @@ -201,7 +201,7 @@ Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of See also `eigen` and `eigh`. """ eig(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - eig!(permute(t, p1, p2; copy = true); kwargs...) + eig!(permute(t, (p1, p2); copy = true); kwargs...) """ eigh(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple) -> D, V @@ -218,13 +218,13 @@ indices of `t` is used. In that case, less memory is allocated if one allows the 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 +permute(t, (leftind, rightind)) * V = V * D ``` See also `eigen` and `eig`. """ function eigh(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) - return eigh!(permute(t, p1, p2; copy=true)) + return eigh!(permute(t, (p1, p2); copy=true)) end """ @@ -242,7 +242,7 @@ Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of matrices. See the corresponding documentation for more information. """ LinearAlgebra.isposdef(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) = - isposdef!(permute(t, p1, p2; copy = true)) + isposdef!(permute(t, (p1, p2); copy = true)) tsvd(t::AbstractTensorMap; trunc::TruncationScheme = NoTruncation(), p::Real = 2, alg::Union{SVD, SDD} = SDD()) = diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index ec4c7145..02b85aa1 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -36,7 +36,7 @@ function permute(t::TensorMap{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}; if !copy if p₁ === codomainind(t) && p₂ === domainind(t) return t - elseif has_shared_permute(t, p) + elseif has_shared_permute(t, (p₁, p₂)) return TensorMap(reshape(t.data, dim(cod), dim(dom)), cod, dom) end end diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 679650a6..caacf4c3 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -84,7 +84,7 @@ function TO.tensortrace!(C::AbstractTensorMap{S}, p::Index2Tuple, end # TODO: novel syntax for tensortrace? # tensortrace!(C, pC′, A′, qA′, α, β, backend...) - trace!(C, A′, p′, q′, α, β, backend...) + trace_permute!(C, A′, p′, q′, α, β, backend...) return C end @@ -153,9 +153,9 @@ TO.tensorcost(t::AbstractTensorMap, i::Int) = dim(space(t, i)) # Trace implementation #---------------------- -function trace!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, +function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}, (q₁, q₂)::Index2Tuple{N₃,N₃}, - backend...) where {S,N₁,N₂,N₃} + α, β, backend...) where {S,N₁,N₂,N₃} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end From 5060bd71f9f5236d0a7d8d88fc6ebffcb9ebe300 Mon Sep 17 00:00:00 2001 From: Jutho Date: Sat, 12 Aug 2023 00:27:17 +0200 Subject: [PATCH 32/57] part2b - tests are working? --- src/planar/planaroperations.jl | 38 ++++++++++++++-------- src/planar/preprocessors.jl | 12 +++++-- src/tensors/indexmanipulations.jl | 2 +- src/tensors/tensoroperations.jl | 53 +++++++++++++++---------------- test/planar.jl | 24 +++++++------- test/tensors.jl | 20 ++++++------ 6 files changed, 84 insertions(+), 65 deletions(-) diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index 45d7e969..51ccbf92 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -1,25 +1,32 @@ # planar versions of tensor operations add!, trace! and contract! -function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N₂}, +function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, - α, β, backend::Backend...) where {S,N₁,N₂} + p::Index2Tuple{N₁,N₂}, + α, + β, + backend::Backend...) where {S,N₁,N₂} return add_transpose!(C, A, p, α, β, backend...) end -function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N₂}, - A::AbstractTensorMap{S}, q::Index2Tuple{N₃,N₃}, - α, β, backend::Backend...) where {S,N₁,N₂,N₃} +function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, + p::Index2Tuple{N₁,N₂}, + q::Index2Tuple{N₃,N₃}, + α, + β, + backend::Backend...) where {S,N₁,N₂,N₃} if BraidingStyle(sectortype(S)) == Bosonic() return trace_permute!(C, A, p, q, α, β, backend...) end @boundscheck begin - all(i -> space(A, pC[1][i]) == space(C, i), 1:N₁) || + all(i -> space(A, p[1][i]) == space(C, i), 1:N₁) || throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), C = $(codomain(C))←$(domain(C)), p1 = $(p1), p2 = $(p2)")) - all(i -> space(A, pC[2][i]) == space(C, N₁ + i), 1:N₂) || + all(i -> space(A, p[2][i]) == space(C, N₁ + i), 1:N₂) || throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), C = $(codomain(C))←$(domain(C)), p1 = $(p1), p2 = $(p2)")) - all(i -> space(A, pA[1][i]) == dual(space(A, pA[2][i])), 1:N₃) || + all(i -> space(A, q[1][i]) == dual(space(A, q[2][i])), 1:N₃) || throw(SpaceMismatch("trace: A = $(codomain(A))←$(domain(A)), q1 = $(q1), q2 = $(q2)")) end @@ -29,19 +36,24 @@ function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, p::Index2Tuple{N₁,N elseif !isone(β) rmul!(C, β) end - pdata = linearize(pC) for (f₁, f₂) in fusiontrees(A) for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, p..., q...) - TO.tensortrace!(C[f₁′, f₂′], p, A[f₁, f₂], q, α * coeff, true, backend...) + TO.tensortrace!(C[f₁′, f₂′], p, A[f₁, f₂], q, :N, α * coeff, true, backend...) end end return C end -function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, pAB::Index2Tuple{N₁,N₂}, - A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, - pB::Index2Tuple, α, β, backend::Backend...) where {S,N₁,N₂} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, + pA::Index2Tuple, + B::AbstractTensorMap{S}, + pB::Index2Tuple, + pAB::Index2Tuple{N₁,N₂}, + α, + β, + backend::Backend...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA = pA diff --git a/src/planar/preprocessors.jl b/src/planar/preprocessors.jl index 498a04fb..d6eb304b 100644 --- a/src/planar/preprocessors.jl +++ b/src/planar/preprocessors.jl @@ -356,8 +356,16 @@ function _decompose_planar_contractions(ex::Expr, temporaries) lhs, rhs = TO.getlhs(ex), TO.getrhs(ex) if TO.istensorexpr(rhs) pre = Vector{Any}() - rhs = _extract_contraction_pairs(rhs, lhs, pre, temporaries) - return Expr(:block, pre..., Expr(ex.head, lhs, rhs)) + if TO.istensor(lhs) + rhs = _extract_contraction_pairs(rhs, lhs, pre, temporaries) + return Expr(:block, pre..., Expr(ex.head, lhs, rhs)) + else + lhssym = gensym(string(lhs)) + lhstensor = Expr(:typed_vcat, lhssym, Expr(:tuple), Expr(:tuple)) + rhs = _extract_contraction_pairs(rhs, lhstensor, pre, temporaries) + push!(temporaries, lhssym) + return Expr(:block, pre..., Expr(:(:=), lhstensor, rhs), Expr(:(=), lhs, lhstensor)) + end else return ex end diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 02b85aa1..a8aa4a1a 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -68,7 +68,7 @@ function has_shared_permute(t::TensorMap, (p₁, p₂)::Index2Tuple) return false end end -function has_shared_permute(t::AdjointTensorMap, p₁, p₂) +function has_shared_permute(t::AdjointTensorMap, (p₁, p₂)::Index2Tuple) p₁′ = adjointtensorindices(t, p₂) p₂′ = adjointtensorindices(t, p₁) return has_shared_permute(t', (p₁′, p₂′)) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index caacf4c3..468177c7 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -216,15 +216,15 @@ end # TODO: contraction with either A or B a rank (1, 1) tensor does not require to # permute the fusion tree and should therefore be special cased. This will speed # up MPS algorithms -function contract!(C::AbstractTensorMap{S,N₁,N₂}, - A::AbstractTensorMap{S}, pA::Index2Tuple, - B::AbstractTensorMap{S}, pB::Index2Tuple, - pAB::Index2Tuple{N₁,N₂}, - α::Number, β::Number, backend...) where {S,N₁,N₂} +function contract!(C::AbstractTensorMap{S}, + A::AbstractTensorMap{S}, (oindA, cindA)::Index2Tuple{N₁,N₃}, + B::AbstractTensorMap{S}, (cindB, oindB)::Index2Tuple{N₃,N₂}, + (p₁, p₂)::Index2Tuple, + α::Number, β::Number, backend...) where {S,N₁,N₂,N₃} # find optimal contraction scheme hsp = has_shared_permute - ipC = TupleTools.invperm(linearize(pAB)) + ipC = TupleTools.invperm((p₁..., p₂...)) oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) @@ -239,32 +239,32 @@ function contract!(C::AbstractTensorMap{S,N₁,N₂}, dA, dB, dC = dim(A), dim(B), dim(C) # keep order A en B, check possibilities for cind - memcost1 = memcost2 = dC * (!hsp(C, oindAinC, oindBinC)) - memcost1 += dA * (!hsp(A, oindA, cindA′)) + - dB * (!hsp(B, cindB′, oindB)) - memcost2 += dA * (!hsp(A, oindA, cindA′′)) + - dB * (!hsp(B, cindB′′, oindB)) + memcost1 = memcost2 = dC * (!hsp(C, (oindAinC, oindBinC))) + memcost1 += dA * (!hsp(A, (oindA, cindA′))) + + dB * (!hsp(B, (cindB′, oindB))) + memcost2 += dA * (!hsp(A, (oindA, cindA′′))) + + dB * (!hsp(B, (cindB′′, oindB))) # reverse order A en B, check possibilities for cind - memcost3 = memcost4 = dC * (!hsp(C, oindBinC, oindAinC)) - memcost3 += dB * (!hsp(B, oindB, cindB′)) + - dA * (!hsp(A, cindA′, oindA)) - memcost4 += dB * (!hsp(B, oindB, cindB′′)) + - dA * (!hsp(A, cindA′′, oindA)) + memcost3 = memcost4 = dC * (!hsp(C, (oindBinC, oindAinC))) + memcost3 += dB * (!hsp(B, (oindB, cindB′))) + + dA * (!hsp(A, (cindA′, oindA))) + memcost4 += dB * (!hsp(B, (oindB, cindB′′))) + + dA * (!hsp(A, (cindA′′, oindA))) if min(memcost1, memcost2) <= min(memcost3, memcost4) if memcost1 <= memcost2 - return _contract!(α, A, B, β, C, oindA, cindA′, oindB, cindB′, p₁, p₂, syms) + return _contract!(α, A, B, β, C, oindA, cindA′, oindB, cindB′, p₁, p₂) else - return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p₁, p₂, syms) + return _contract!(α, A, B, β, C, oindA, cindA′′, oindB, cindB′′, p₁, p₂) end else p1′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p₁) p2′ = map(n -> ifelse(n > N₁, n - N₁, n + N₂), p₂) if memcost3 <= memcost4 - return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′, syms) + return _contract!(α, B, A, β, C, oindB, cindB′, oindA, cindA′, p1′, p2′) else - return _contract!(α, B, A, β, C, oindB, cindB′′, oindA, cindA′′, p1′, p2′, syms) + return _contract!(α, B, A, β, C, oindB, cindB′′, oindA, cindA′′, p1′, p2′) end end end @@ -273,8 +273,7 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, β, C::AbstractTensorMap{S}, oindA::IndexTuple{N₁}, cindA::IndexTuple, oindB::IndexTuple{N₂}, cindB::IndexTuple, - p₁::IndexTuple, p₂::IndexTuple, - syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S,N₁,N₂} + p₁::IndexTuple, p₂::IndexTuple) where {S,N₁,N₂} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @@ -286,8 +285,8 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, end end end - A′ = permute(A, oindA, cindA; copy=copyA) - B′ = permute(B, cindB, oindB) + A′ = permute(A, (oindA, cindA); copy=copyA) + B′ = permute(B, (cindB, oindB)) if BraidingStyle(sectortype(S)) isa Fermionic for i in domainind(A′) if !isdual(space(A′, i)) @@ -298,12 +297,12 @@ function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, ipC = TupleTools.invperm((p₁..., p₂...)) oindAinC = TupleTools.getindices(ipC, ntuple(n -> n, N₁)) oindBinC = TupleTools.getindices(ipC, ntuple(n -> n + N₁, N₂)) - if has_shared_permute(C, oindAinC, oindBinC) - C′ = permute(C, oindAinC, oindBinC) + if has_shared_permute(C, (oindAinC, oindBinC)) + C′ = permute(C, (oindAinC, oindBinC)) mul!(C′, A′, B′, α, β) else C′ = A′ * B′ - add!(α, C′, β, C, p₁, p₂) + add_permute!(C, C′, (p₁, p₂), α, β) end return C end diff --git a/test/planar.jl b/test/planar.jl index 8610f48b..ff958752 100644 --- a/test/planar.jl +++ b/test/planar.jl @@ -13,13 +13,13 @@ function force_planar(V::GradedSpace) end force_planar(V::ProductSpace) = mapreduce(force_planar, ⊗, V) function force_planar(tsrc::TensorMap{ComplexSpace}) - tdst = TensorMap(undef, eltype(tsrc), + tdst = TensorMap(undef, scalartype(tsrc), force_planar(codomain(tsrc)) ← force_planar(domain(tsrc))) copyto!(blocks(tdst)[PlanarTrivial()], blocks(tsrc)[Trivial()]) return tdst end function force_planar(tsrc::TensorMap{<:GradedSpace}) - tdst = TensorMap(undef, eltype(tsrc), + tdst = TensorMap(undef, scalartype(tsrc), force_planar(codomain(tsrc)) ← force_planar(domain(tsrc))) for (c, b) in blocks(tsrc) copyto!(blocks(tdst)[c ⊠ PlanarTrivial()], b) @@ -33,10 +33,10 @@ end C = TensorMap(randn, (ℂ^5)' ⊗ (ℂ^6)' ← ℂ^4 ⊗ (ℂ^3)' ⊗ (ℂ^2)') A′ = force_planar(A) C′ = force_planar(C) - pC = ((4, 3), (5, 2, 1)) + p = ((4, 3), (5, 2, 1)) - @test force_planar(tensoradd!(C, pC, A, :N, true, true)) ≈ - planaradd!(C′, pC, A′, true, true) + @test force_planar(tensoradd!(C, p, A, :N, true, true)) ≈ + planaradd!(C′, A′, p, true, true) end @testset "planartrace" begin @@ -44,11 +44,11 @@ end C = TensorMap(randn, (ℂ^5)' ⊗ ℂ^3 ← ℂ^4) A′ = force_planar(A) C′ = force_planar(C) - pA = ((1,), (3,)) - pC = ((4, 2), (5,)) + p = ((4, 2), (5,)) + q = ((1,), (3,)) - @test force_planar(tensortrace!(C, pC, A, pA, :N, true, true)) ≈ - planartrace!(C′, pC, A′, pA, true, true) + @test force_planar(tensortrace!(C, p, A, q, :N, true, true)) ≈ + planartrace!(C′, A′, p, q, true, true) end @testset "planarcontract" begin @@ -62,10 +62,10 @@ end pA = ((1, 3, 4), (5, 2)) pB = ((2, 4), (1, 3)) - pC = ((3, 2, 1), (4, 5)) + pAB = ((3, 2, 1), (4, 5)) - @test force_planar(tensorcontract!(C, pC, A, pA, :N, B, pB, :N, true, true)) ≈ - planarcontract!(C′, pC, A′, pA, B′, pB, true, true) + @test force_planar(tensorcontract!(C, pAB, A, pA, :N, B, pB, :N, true, true)) ≈ + planarcontract!(C′, A′, pA, B′, pB, pAB, true, true) end end diff --git a/test/tensors.jl b/test/tensors.jl index 09927bdc..24401ac7 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -193,9 +193,9 @@ for V in spacelist for p in permutations(1:5) p1 = ntuple(n -> p[n], k) p2 = ntuple(n -> p[k + n], 5 - k) - t2 = @constinferred permute(t, p1, p2) + t2 = @constinferred permute(t, (p1, p2)) @test norm(t2) ≈ norm(t) - t2′ = permute(t′, p1, p2) + t2′ = permute(t′, (p1, p2)) @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) end end @@ -208,7 +208,7 @@ for V in spacelist for p in permutations(1:5) p1 = ntuple(n -> p[n], k) p2 = ntuple(n -> p[k + n], 5 - k) - t2 = permute(t, p1, p2) + t2 = permute(t, (p1, p2)) a2 = convert(Array, t2) @test a2 ≈ permutedims(convert(Array, t), (p1..., p2...)) @test convert(Array, transpose(t2)) ≈ permutedims(a2, (5, 4, 3, 2, 1)) @@ -218,7 +218,7 @@ for V in spacelist end @timedtestset "Full trace: test self-consistency" begin t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') - t2 = permute(t, (1, 2), (4, 3)) + t2 = permute(t, ((1, 2), (4, 3))) s = @constinferred tr(t2) @test conj(s) ≈ tr(t2') if !isdual(V1) @@ -345,7 +345,7 @@ for V in spacelist Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) QdQ = Q' * Q @test QdQ ≈ one(QdQ) - @test Q * R ≈ permute(t, (3, 4, 2), (1, 5)) + @test Q * R ≈ permute(t, ((3, 4, 2), (1, 5))) if alg isa Polar @test isposdef(R) @test domain(R) == codomain(R) == space(t, 1)' ⊗ space(t, 5)' @@ -356,7 +356,7 @@ for V in spacelist N = @constinferred leftnull(t, (3, 4, 2), (1, 5); alg=alg) NdN = N' * N @test NdN ≈ one(NdN) - @test norm(N' * permute(t, (3, 4, 2), (1, 5))) < 100 * eps(norm(t)) + @test norm(N' * permute(t, ((3, 4, 2), (1, 5)))) < 100 * eps(norm(t)) end @testset "rightorth with $alg" for alg in (TensorKit.RQ(), TensorKit.RQpos(), @@ -366,7 +366,7 @@ for V in spacelist L, Q = @constinferred rightorth(t, (3, 4), (2, 1, 5); alg=alg) QQd = Q * Q' @test QQd ≈ one(QQd) - @test L * Q ≈ permute(t, (3, 4), (2, 1, 5)) + @test L * Q ≈ permute(t, ((3, 4), (2, 1, 5))) if alg isa Polar @test isposdef(L) @test domain(L) == codomain(L) == space(t, 3) ⊗ space(t, 4) @@ -377,7 +377,7 @@ for V in spacelist M = @constinferred rightnull(t, (3, 4), (2, 1, 5); alg=alg) MMd = M * M' @test MMd ≈ one(MMd) - @test norm(permute(t, (3, 4), (2, 1, 5)) * M') < 100 * eps(norm(t)) + @test norm(permute(t, ((3, 4), (2, 1, 5))) * M') < 100 * eps(norm(t)) end @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) U, S, V = @constinferred tsvd(t, (3, 4, 2), (1, 5); alg=alg) @@ -385,7 +385,7 @@ for V in spacelist @test UdU ≈ one(UdU) VVd = V * V' @test VVd ≈ one(VVd) - @test U * S * V ≈ permute(t, (3, 4, 2), (1, 5)) + @test U * S * V ≈ permute(t, ((3, 4, 2), (1, 5))) end end @testset "empty tensor" begin @@ -436,7 +436,7 @@ for V in spacelist VdV = V' * V VdV = (VdV + VdV') / 2 @test isposdef(VdV) - t2 = permute(t, (1, 3), (2, 4)) + t2 = permute(t, ((1, 3), (2, 4))) @test t2 * V ≈ V * D @test !isposdef(t2) # unlikely for non-hermitian map t2 = (t2 + t2') From 4cc89dfaa3904190a024591c19b8bfdc9b3a061d Mon Sep 17 00:00:00 2001 From: Jutho Date: Sat, 12 Aug 2023 11:34:21 +0200 Subject: [PATCH 33/57] format relevant files and small changes; add TODO --- src/planar/analyzers.jl | 25 ++++---- src/planar/macros.jl | 26 ++++---- src/planar/planaroperations.jl | 99 +++++-------------------------- src/planar/postprocessors.jl | 12 ++-- src/planar/preprocessors.jl | 87 +++++++++++++++------------ src/tensors/indexmanipulations.jl | 5 +- src/tensors/tensoroperations.jl | 26 +++++--- 7 files changed, 120 insertions(+), 160 deletions(-) diff --git a/src/planar/analyzers.jl b/src/planar/analyzers.jl index 5cb577b7..b91ff2ba 100644 --- a/src/planar/analyzers.jl +++ b/src/planar/analyzers.jl @@ -6,13 +6,13 @@ function get_possible_planar_indices(ex) if !TO.istensorexpr(ex) return [[]] elseif TO.isgeneraltensor(ex) - _,leftind,rightind = TO.decomposegeneraltensor(ex) + _, leftind, rightind = TO.decomposegeneraltensor(ex) ind = planar_unique2(vcat(leftind, reverse(rightind))) return length(ind) == length(unique(ind)) ? Any[ind] : Any[] elseif isexpr(ex, :call) && (ex.args[1] == :+ || ex.args[1] == :-) inds = get_possible_planar_indices(ex.args[2]) keep = fill(true, length(inds)) - for i = 3:length(ex.args) + for i in 3:length(ex.args) inds′ = get_possible_planar_indices(ex.args[i]) keepᵢ = fill(false, length(inds)) for (j, ind) in enumerate(inds), ind′ in inds′ @@ -53,7 +53,7 @@ function planar_unique2(allind) removing = false i = 1 while i <= length(oind) && length(oind) > 1 - j = mod1(i+1, length(oind)) + j = mod1(i + 1, length(oind)) if oind[i] == oind[j] deleteat!(oind, i) deleteat!(oind, mod1(i, length(oind))) @@ -76,21 +76,21 @@ function possible_planar_complements(ind1, ind2) # general case: j1 = findfirst(in(ind2), ind1) if j1 === nothing # disconnected diagrams, can be made planar in various ways - return Any[(circshift(ind1, i-1), circshift(ind2, j-1), Any[], Any[]) - for i ∈ eachindex(ind1), j ∈ eachindex(ind2)] + return Any[(circshift(ind1, i - 1), circshift(ind2, j - 1), Any[], Any[]) + for i in eachindex(ind1), j in eachindex(ind2)] else # genuine contraction N1, N2 = length(ind1), length(ind2) j2 = findfirst(==(ind1[j1]), ind2) jmax1 = j1 jmin2 = j2 - while jmax1 < N1 && ind1[jmax1+1] == ind2[mod1(jmin2-1, N2)] + while jmax1 < N1 && ind1[jmax1 + 1] == ind2[mod1(jmin2 - 1, N2)] jmax1 += 1 jmin2 -= 1 end jmin1 = j1 jmax2 = j2 if j1 == 1 && jmax1 < N1 - while ind1[mod1(jmin1-1, N1)] == ind2[mod1(jmax2 + 1, N2)] + while ind1[mod1(jmin1 - 1, N1)] == ind2[mod1(jmax2 + 1, N2)] jmin1 -= 1 jmax2 += 1 end @@ -99,11 +99,12 @@ function possible_planar_complements(ind1, ind2) jmax2 -= N2 jmin2 -= N2 end - indo1 = jmin1 < 1 ? ind1[(jmax1+1):mod1(jmin1-1, N1)] : - vcat(ind1[(jmax1+1):N1], ind1[1:(jmin1-1)]) - cind1 = jmin1 < 1 ? vcat(ind1[mod1(jmin1, N1):N1], ind1[1:jmax1]) : ind1[jmin1:jmax1] - indo2 = jmin2 < 1 ? ind2[(jmax2+1):mod1(jmin2-1, N2)] : - vcat(ind2[(jmax2+1):N2], ind2[1:(jmin2-1)]) + indo1 = jmin1 < 1 ? ind1[(jmax1 + 1):mod1(jmin1 - 1, N1)] : + vcat(ind1[(jmax1 + 1):N1], ind1[1:(jmin1 - 1)]) + cind1 = jmin1 < 1 ? vcat(ind1[mod1(jmin1, N1):N1], ind1[1:jmax1]) : + ind1[jmin1:jmax1] + indo2 = jmin2 < 1 ? ind2[(jmax2 + 1):mod1(jmin2 - 1, N2)] : + vcat(ind2[(jmax2 + 1):N2], ind2[1:(jmin2 - 1)]) cind2 = reverse(cind1) return isempty(intersect(indo1, indo2)) ? Any[(indo1, indo2, cind1, cind2)] : Any[] end diff --git a/src/planar/macros.jl b/src/planar/macros.jl index 09dbe6a7..e6d3c232 100644 --- a/src/planar/macros.jl +++ b/src/planar/macros.jl @@ -4,7 +4,7 @@ macro planar(args::Vararg{Expr}) planarexpr = args[end] kwargs = TO.parse_tensor_kwargs(args[1:(end - 1)]) - parser = planarparser(planarexpr, kwargs... ) + parser = planarparser(planarexpr, kwargs...) return esc(parser(planarexpr)) end @@ -17,8 +17,8 @@ function planarparser(planarexpr, kwargs...) push!(parser.preprocessors, _extract_tensormap_objects) temporaries = Vector{Symbol}() - push!(parser.postprocessors, ex->_annotate_temporaries(ex, temporaries)) - push!(parser.postprocessors, ex->_free_temporaries(ex, temporaries)) + push!(parser.postprocessors, ex -> _annotate_temporaries(ex, temporaries)) + push!(parser.postprocessors, ex -> _free_temporaries(ex, temporaries)) push!(parser.postprocessors, _insert_planar_operations) for kw in kwargs @@ -28,7 +28,8 @@ function planarparser(planarexpr, kwargs...) isexpr(val, :tuple) || throw(ArgumentError("Invalid use of `order`, should be `order=(...,)`")) indexorder = map(normalizeindex, val.args) - parser.contractiontreebuilder = network -> TO.indexordertree(network, indexorder) + parser.contractiontreebuilder = network -> TO.indexordertree(network, + indexorder) elseif name == :contractcheck val isa Bool || @@ -66,11 +67,12 @@ function planarparser(planarexpr, kwargs...) treebuilder = parser.contractiontreebuilder treesorter = parser.contractiontreesorter costcheck = parser.contractioncostcheck - push!(parser.preprocessors, ex->TO.processcontractions(ex, treebuilder, treesorter, costcheck)) + push!(parser.preprocessors, + ex -> TO.processcontractions(ex, treebuilder, treesorter, costcheck)) parser.contractioncostcheck = nothing - push!(parser.preprocessors, ex->_check_planarity(ex)) - push!(parser.preprocessors, ex->_decompose_planar_contractions(ex, temporaries)) - + push!(parser.preprocessors, ex -> _check_planarity(ex)) + push!(parser.preprocessors, ex -> _decompose_planar_contractions(ex, temporaries)) + return parser end @@ -87,11 +89,11 @@ function _plansor(expr, kwargs...) newtensors = TO.getnewtensorobjects(expr) # find the first non-braiding tensor to determine the braidingstyle - targetobj = inputtensors[findfirst(x->x != :τ, inputtensors)] + targetobj = inputtensors[findfirst(x -> x != :τ, inputtensors)] if !isa(targetobj, Symbol) targetsym = gensym(string(targetobj)) expr = TO.replacetensorobjects(expr) do obj, leftind, rightind - obj == targetobj ? targetsym : obj + return obj == targetobj ? targetsym : obj end args = Any[(:($targetsym = $targetobj))] else @@ -105,7 +107,9 @@ function _plansor(expr, kwargs...) tensorex = tparser(expr) planarex = pparser(expr) - push!(args, Expr(:if, :(BraidingStyle(sectortype($targetsym)) isa Bosonic), tensorex, planarex)) + push!(args, + Expr(:if, :(BraidingStyle(sectortype($targetsym)) isa Bosonic), tensorex, + planarex)) if !isa(targetobj, Symbol) && targetobj ∈ newtensors push!(args, :($targetobj = $targetsym)) end diff --git a/src/planar/planaroperations.jl b/src/planar/planaroperations.jl index 51ccbf92..9f12de11 100644 --- a/src/planar/planaroperations.jl +++ b/src/planar/planaroperations.jl @@ -2,18 +2,18 @@ function planaradd!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, p::Index2Tuple{N₁,N₂}, - α, - β, + α::Number, + β::Number, backend::Backend...) where {S,N₁,N₂} return add_transpose!(C, A, p, α, β, backend...) end -function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, +function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, p::Index2Tuple{N₁,N₂}, q::Index2Tuple{N₃,N₃}, - α, - β, + α::Number, + β::Number, backend::Backend...) where {S,N₁,N₂,N₃} if BraidingStyle(sectortype(S)) == Bosonic() return trace_permute!(C, A, p, q, α, β, backend...) @@ -44,21 +44,25 @@ function planartrace!(C::AbstractTensorMap{S,N₁,N₂}, return C end - -function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, A::AbstractTensorMap{S}, pA::Index2Tuple, B::AbstractTensorMap{S}, pB::Index2Tuple, pAB::Index2Tuple{N₁,N₂}, - α, - β, + α::Number, + β::Number, backend::Backend...) where {S,N₁,N₂} + if BraidingStyle(sectortype(S)) == Bosonic() + return contract(C, A, pA, B, pB, pAB, α, β, backend...) + end + codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA = pA cindB, oindB = pB - oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, pAB...) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, pAB...) if oindA == codA && cindA == domA A′ = A @@ -80,81 +84,6 @@ function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, return C end -# function planaradd!(α, tsrc::AbstractTensorMap{S}, -# β, tdst::AbstractTensorMap{S,N₁,N₂}, -# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {S,N₁,N₂} -# return add_transpose!(α, tsrc, β, tdst, p1, p2) -# end - -# function planar_trace!(α, tsrc::AbstractTensorMap{S}, -# β, tdst::AbstractTensorMap{S,N₁,N₂}, -# p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, -# q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {S,N₁,N₂,N₃} -# if BraidingStyle(sectortype(S)) == Bosonic() -# return trace!(α, tsrc, β, tdst, p1, p2, q1, q2) -# end - -# @boundscheck begin -# all(i -> space(tsrc, p1[i]) == space(tdst, i), 1:N₁) || -# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), -# tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) -# all(i -> space(tsrc, p2[i]) == space(tdst, N₁ + i), 1:N₂) || -# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), -# tdst = $(codomain(tdst))←$(domain(tdst)), p1 = $(p1), p2 = $(p2)")) -# all(i -> space(tsrc, q1[i]) == dual(space(tsrc, q2[i])), 1:N₃) || -# throw(SpaceMismatch("trace: tsrc = $(codomain(tsrc))←$(domain(tsrc)), -# q1 = $(q1), q2 = $(q2)")) -# end - -# if iszero(β) -# fill!(tdst, β) -# elseif β != 1 -# rmul!(tdst, β) -# end -# pdata = (p1..., p2...) -# for (f₁, f₂) in fusiontrees(tsrc) -# for ((f₁′, f₂′), coeff) in planar_trace(f₁, f₂, p1, p2, q1, q2) -# TO._trace!(α * coeff, tsrc[f₁, f₂], true, tdst[f₁′, f₂′], pdata, q1, q2) -# end -# end -# return tdst -# end - -# function planar_contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, -# β, C::AbstractTensorMap{S}, -# oindA::IndexTuple, cindA::IndexTuple, -# oindB::IndexTuple, cindB::IndexTuple, -# p1::IndexTuple, p2::IndexTuple, -# syms::Union{Nothing,NTuple{3,Symbol}}=nothing) where {S} -# codA, domA = codomainind(A), domainind(A) -# codB, domB = codomainind(B), domainind(B) -# oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, -# oindB, cindB, p1, p2) - -# if oindA == codA && cindA == domA -# A′ = A -# else -# if isnothing(syms) -# A′ = TO.similar_from_indices(eltype(A), oindA, cindA, A, :N) -# else -# A′ = TO.cached_similar_from_indices(syms[1], eltype(A), oindA, cindA, A, :N) -# end -# add_transpose!(true, A, false, A′, oindA, cindA) -# end -# if cindB == codB && oindB == domB -# B′ = B -# else -# if isnothing(syms) -# B′ = TO.similar_from_indices(eltype(B), cindB, oindB, B, :N) -# else -# B′ = TO.cached_similar_from_indices(syms[2], eltype(B), cindB, oindB, B, :N) -# end -# add_transpose!(true, B, false, B′, cindB, oindB) -# end -# mul!(C, A′, B′, α, β) -# return C -# end - # auxiliary routines _cyclicpermute(t::Tuple) = (Base.tail(t)..., t[1]) _cyclicpermute(t::Tuple{}) = () diff --git a/src/planar/postprocessors.jl b/src/planar/postprocessors.jl index 1daa1836..7e9bd8e5 100644 --- a/src/planar/postprocessors.jl +++ b/src/planar/postprocessors.jl @@ -5,13 +5,13 @@ # to correct for this by adding the `istemp = true` flag. function _annotate_temporaries(ex, temporaries) if isexpr(ex, :(=)) && isexpr(ex.args[2], :call) && - ex.args[2].args[1] ∈ (:tensoralloc_add, :tensoralloc_contract) + ex.args[2].args[1] ∈ (:tensoralloc_add, :tensoralloc_contract) lhs = ex.args[1] i = findfirst(==(lhs), temporaries) if i !== nothing rhs = ex.args[2] # add `istemp = true` flag - newrhs = Expr(:call, rhs.args[1:end-1]..., true) + newrhs = Expr(:call, rhs.args[1:(end - 1)]..., true) return Expr(:(=), lhs, newrhs) end elseif ex isa Expr @@ -40,7 +40,7 @@ function _free_temporaries(ex, temporaries) push!(newargs, Expr(:call, :tensorfree!, t)) push!(newargs, lhs) else - newargs = insert!(newargs, i+1, Expr(:call, :tensorfree!, t)) + newargs = insert!(newargs, i + 1, Expr(:call, :tensorfree!, t)) end end return Expr(:block, newargs...) @@ -53,7 +53,9 @@ end # NOTE: work around a somewhat unfortunate interface choice in TensorOperations, which we will correct in the future. _planaradd!(C, p, A, α, β, backend...) = planaradd!(C, A, p, α, β, backend...) _planartrace!(C, p, A, q, α, β, backend...) = planartrace!(C, A, p, q, α, β, backend...) -_planarcontract!(C, pAB, A, pA, B, pB, α, β, backend...) = planarcontract!(C, A, pA, B, pB, pAB, α, β, backend...) +function _planarcontract!(C, pAB, A, pA, B, pB, α, β, backend...) + return planarcontract!(C, A, pA, B, pB, pAB, α, β, backend...) +end # TODO: replace _planarmethod with planarmethod in everything below const _PLANAR_OPERATIONS = (:_planaradd!, :_planartrace!, :_planarcontract!) @@ -97,4 +99,4 @@ function insert_operationbackend(ex, backend) else return ex end -end \ No newline at end of file +end diff --git a/src/planar/preprocessors.jl b/src/planar/preprocessors.jl index d6eb304b..c974c47e 100644 --- a/src/planar/preprocessors.jl +++ b/src/planar/preprocessors.jl @@ -6,7 +6,7 @@ function _conj_to_adjoint(ex) if isexpr(ex, :call) && ex.args[1] == :conj && TO.istensor(ex.args[2]) obj, leftind, rightind = TO.decomposetensor(ex.args[2]) return Expr(:typed_vcat, Expr(TO.prime, obj), - Expr(:tuple, rightind...), Expr(:tuple, leftind...)) + Expr(:tuple, rightind...), Expr(:tuple, leftind...)) elseif ex isa Expr return Expr(ex.head, [_conj_to_adjoint(a) for a in ex.args]...) else @@ -27,8 +27,10 @@ function _extract_tensormap_objects(ex) @assert !any(_is_adjoint, newtensors) existingtensors = unique!(vcat(inputtensors, outputtensors)) alltensors = unique!(vcat(existingtensors, newtensors)) - tensordict = Dict{Any,Any}(a => gensym(string(a)) for a in alltensors if !(a isa Symbol)) - pre = Expr(:block, [Expr(:(=), tensordict[a], a) for a in existingtensors if !(a isa Symbol)]...) + tensordict = Dict{Any,Any}(a => gensym(string(a)) + for a in alltensors if !(a isa Symbol)) + pre = Expr(:block, + [Expr(:(=), tensordict[a], a) for a in existingtensors if !(a isa Symbol)]...) pre2 = Expr(:block) ex = TO.replacetensorobjects(ex) do obj, leftind, rightind _is_adj = _is_adjoint(obj) @@ -47,17 +49,21 @@ function _extract_tensormap_objects(ex) errorstr2 = " for $objstr." checksize = quote $nlsym, $nrsym = numout($newobj), numin($newobj) - (numout($newobj) == $nl && $numin($newobj)== $nr) || + (numout($newobj) == $nl && $numin($newobj) == $nr) || throw(IndexError($errorstr1 * string(($nlsym, $nrsym)) * $errorstr2)) end push!(pre2.args, checksize) end return _is_adj ? _add_adjoint(newobj) : newobj end - post = Expr(:block, [Expr(:(=), a, tensordict[a]) for a in newtensors if !(a isa Symbol)]...) - pre = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre) - pre2 = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre2) - post = Expr(:macrocall, Symbol("@notensor"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), post) + post = Expr(:block, + [Expr(:(=), a, tensordict[a]) for a in newtensors if !(a isa Symbol)]...) + pre = Expr(:macrocall, Symbol("@notensor"), + LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre) + pre2 = Expr(:macrocall, Symbol("@notensor"), + LineNumberNode(@__LINE__, Symbol(@__FILE__)), pre2) + post = Expr(:macrocall, Symbol("@notensor"), + LineNumberNode(@__LINE__, Symbol(@__FILE__)), post) return Expr(:block, pre, pre2, ex, post) end _is_adjoint(ex) = isexpr(ex, TO.prime) @@ -73,7 +79,7 @@ function _construct_braidingtensors(ex::Expr) if TO.isassignment(ex) && TO.istensor(lhs) obj, l, r = TO.decomposetensor(lhs) lhs_as_rhs = Expr(:typed_vcat, _add_adjoint(obj), - Expr(:tuple, r...), Expr(:tuple, l...)) + Expr(:tuple, r...), Expr(:tuple, l...)) push!(list, lhs_as_rhs) end else @@ -124,7 +130,7 @@ function _construct_braidingtensors(ex::Expr) indexmap[i1b] = Expr(TO.prime, Expr(:call, :space, obj_and_pos1b...)) indexmap[i1a] = Expr(TO.prime, Expr(:call, :space, obj_and_pos1b...)) else - push!(unresolved,(i1a,i1b)) + push!(unresolved, (i1a, i1b)) end if !isnothing(obj_and_pos2a) @@ -134,7 +140,7 @@ function _construct_braidingtensors(ex::Expr) indexmap[i2b] = Expr(TO.prime, Expr(:call, :space, obj_and_pos2b...)) indexmap[i2a] = Expr(TO.prime, Expr(:call, :space, obj_and_pos2b...)) else - push!(unresolved,(i2a,i2b)) + push!(unresolved, (i2a, i2b)) end end # simple loop that tries to resolve as many indices as possible @@ -159,8 +165,8 @@ function _construct_braidingtensors(ex::Expr) end !isempty(unresolved) && throw(ArgumentError("cannot determine the spaces of indices " * - string(tuple(unresolved...)) * - "for the braiding tensors in $(ex)")) + string(tuple(unresolved...)) * + "for the braiding tensors in $(ex)")) pre = Expr(:block) for (t, construct_expr) in translatebraidings @@ -178,7 +184,7 @@ function _construct_braidingtensors(ex::Expr) push!(pre.args, Expr(:(=), s, construct_expr)) ex = TO.replacetensorobjects(ex) do o, l, r if o == obj && l == leftind && r == rightind - return obj == :τ ? s : Expr(TO.prime, s) + return obj == :τ ? s : Expr(TO.prime, s) else return o end @@ -284,7 +290,7 @@ function _remove_braidingtensors(ex) while changed == true changed = false i = 1 - for (k,v) in indexmap + for (k, v) in indexmap if v in keys(indexmap) && indexmap[v] != v changed = true indexmap[k] = indexmap[v] @@ -299,17 +305,18 @@ end function _purge_braidingtensors(ex) # actually remove the braidingtensors ex isa Expr || return ex args = collect(filter(ex.args) do a - if isexpr(a, :call) && a.args[1] == :conj - a = a.args[2] - end - if a isa Expr && TO.istensor(a) && _remove_adjoint(TO.gettensorobject(a)) == :τ - _, leftind, rightind = TO.decomposetensor(a) - (leftind[1] == rightind[2] && leftind[2] == rightind[1]) || - throw(ArgumentError("unable to remove braiding tensor $a")) - return false - end - return true - end) + if isexpr(a, :call) && a.args[1] == :conj + a = a.args[2] + end + if a isa Expr && TO.istensor(a) && + _remove_adjoint(TO.gettensorobject(a)) == :τ + _, leftind, rightind = TO.decomposetensor(a) + (leftind[1] == rightind[2] && leftind[2] == rightind[1]) || + throw(ArgumentError("unable to remove braiding tensor $a")) + return false + end + return true + end) # multiplication with only a single argument is (rightfully) seen as invalid syntax if isexpr(ex, :call) && args[1] == :* && length(args) == 2 @@ -339,7 +346,7 @@ function _check_planarity(ex) end else foreach(ex.args) do a - _check_planarity(a) + return _check_planarity(a) end end return ex @@ -364,7 +371,8 @@ function _decompose_planar_contractions(ex::Expr, temporaries) lhstensor = Expr(:typed_vcat, lhssym, Expr(:tuple), Expr(:tuple)) rhs = _extract_contraction_pairs(rhs, lhstensor, pre, temporaries) push!(temporaries, lhssym) - return Expr(:block, pre..., Expr(:(:=), lhstensor, rhs), Expr(:(=), lhs, lhstensor)) + return Expr(:block, pre..., Expr(:(:=), lhstensor, rhs), + Expr(:(=), lhs, lhstensor)) end else return ex @@ -424,13 +432,17 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) end ind1, ind2, oind1, oind2, cind1, cind2 = only(rhs_inds) # inds_rhs should hold exactly one match if all(in(leftind), oind2) || all(in(rightind), oind1) # reverse order - a1 = _extract_contraction_pairs(rhs.args[3], (oind2, reverse(cind2)), pre, temporaries) - a2 = _extract_contraction_pairs(rhs.args[2], (cind1, reverse(oind1)), pre, temporaries) + a1 = _extract_contraction_pairs(rhs.args[3], (oind2, reverse(cind2)), pre, + temporaries) + a2 = _extract_contraction_pairs(rhs.args[2], (cind1, reverse(oind1)), pre, + temporaries) oind1, oind2 = oind2, oind1 cind1, cind2 = cind2, cind1 else - a1 = _extract_contraction_pairs(rhs.args[2], (oind1, reverse(cind1)), pre, temporaries) - a2 = _extract_contraction_pairs(rhs.args[3], (cind2, reverse(oind2)), pre, temporaries) + a1 = _extract_contraction_pairs(rhs.args[2], (oind1, reverse(cind1)), pre, + temporaries) + a2 = _extract_contraction_pairs(rhs.args[3], (cind2, reverse(oind2)), pre, + temporaries) end # @show a1, a2, oind1, oind2 @@ -438,7 +450,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) rhs = Expr(:call, :*, a1, a2) s = gensym() newlhs = Expr(:typed_vcat, s, Expr(:tuple, oind1...), - Expr(:tuple, reverse(oind2)...)) + Expr(:tuple, reverse(oind2)...)) push!(temporaries, s) push!(pre, Expr(:(:=), newlhs, rhs)) return newlhs @@ -457,7 +469,7 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) rhs = Expr(:call, :*, a1, a2) s = gensym() newlhs = Expr(:typed_vcat, s, Expr(:tuple, oind1...), - Expr(:tuple, reverse(oind2)...)) + Expr(:tuple, reverse(oind2)...)) push!(temporaries, s) push!(pre, Expr(:(:=), newlhs, rhs)) return newlhs @@ -469,15 +481,16 @@ function _extract_contraction_pairs(rhs, lhs, pre, temporaries) rhs = Expr(:call, :*, a1, a2) s = gensym() newlhs = Expr(:typed_vcat, s, Expr(:tuple, oind1...), - Expr(:tuple, reverse(oind2)...)) + Expr(:tuple, reverse(oind2)...)) push!(temporaries, s) push!(pre, Expr(:(:=), newlhs, rhs)) return newlhs end end elseif isexpr(rhs, :call) && rhs.args[1] ∈ (:+, :-) - args = [_extract_contraction_pairs(a, lhs, pre, temporaries) for - a in rhs.args[2:end]] + args = [_extract_contraction_pairs(a, lhs, pre, temporaries) + for + a in rhs.args[2:end]] return Expr(rhs.head, rhs.args[1], args...) else throw(ArgumentError("unknown tensor expression")) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index a8aa4a1a..19208fdd 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -333,7 +333,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...) + TO.tensoradd!(tdst[f₁′, f₂′], p, tsrc[f₁, f₂], :N, α * coeff, true, + backend...) end end end @@ -348,4 +349,4 @@ function _add_sectors!(tdst, tsrc, p, fusiontreetransform, s₁, s₂, α, β, b end end return nothing -end \ No newline at end of file +end diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 468177c7..f567535b 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -137,7 +137,8 @@ function TO.tensorcontract_structure(pC::Index2Tuple{N₁,N₂}, end function TO.checkcontractible(tA::AbstractTensorMap{S}, iA::Int, conjA::Symbol, - tB::AbstractTensorMap{S}, iB::Int, conjB::Symbol, label) where {S} + tB::AbstractTensorMap{S}, iB::Int, conjB::Symbol, + label) where {S} sA = TO.tensorstructure(tA, iA, conjA)' sB = TO.tensorstructure(tB, iB, conjB) sA == sB || @@ -153,9 +154,13 @@ TO.tensorcost(t::AbstractTensorMap, i::Int) = dim(space(t, i)) # Trace implementation #---------------------- -function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, tsrc::AbstractTensorMap{S}, - (p₁, p₂)::Index2Tuple{N₁,N₂}, (q₁, q₂)::Index2Tuple{N₃,N₃}, - α, β, backend...) where {S,N₁,N₂,N₃} +function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, + tsrc::AbstractTensorMap{S}, + (p₁, p₂)::Index2Tuple{N₁,N₂}, + (q₁, q₂)::Index2Tuple{N₃,N₃}, + α::Number, + β::Number, + backend::Backend...) where {S,N₁,N₂,N₃} if !(BraidingStyle(sectortype(S)) isa SymmetricBraiding) throw(SectorMismatch("only tensors with symmetric braiding rules can be contracted; try `@planar` instead")) end @@ -217,10 +222,14 @@ end # permute the fusion tree and should therefore be special cased. This will speed # up MPS algorithms function contract!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, (oindA, cindA)::Index2Tuple{N₁,N₃}, - B::AbstractTensorMap{S}, (cindB, oindB)::Index2Tuple{N₃,N₂}, - (p₁, p₂)::Index2Tuple, - α::Number, β::Number, backend...) where {S,N₁,N₂,N₃} + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{N₁,N₃}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{N₃,N₂}, + (p₁, p₂)::Index2Tuple, + α::Number, + β::Number, + backend::Backend...) where {S,N₁,N₂,N₃} # find optimal contraction scheme hsp = has_shared_permute @@ -269,6 +278,7 @@ function contract!(C::AbstractTensorMap{S}, end end +# TODO: also transform _contract! into new interface, and add backend support function _contract!(α, A::AbstractTensorMap{S}, B::AbstractTensorMap{S}, β, C::AbstractTensorMap{S}, oindA::IndexTuple{N₁}, cindA::IndexTuple, From 600ea554cc4a342c841b001a3a5d560d4286bfa1 Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Sun, 13 Aug 2023 14:44:57 +0200 Subject: [PATCH 34/57] Formatter (#75) --- benchmark/benchtools.jl | 96 ++-- benchmark/itensors_timers.jl | 135 ++--- benchmark/plotmera.jl | 40 +- benchmark/plotmpo.jl | 39 +- benchmark/plotpepo.jl | 40 +- benchmark/run_itensors.jl | 111 ++-- benchmark/run_tensorkit.jl | 115 ++-- benchmark/tensorkit_timers.jl | 74 +-- docs/make.jl | 22 +- src/TensorKit.jl | 42 +- src/auxiliary/auxiliary.jl | 16 +- src/auxiliary/deprecate.jl | 2 + src/auxiliary/dicts.jl | 84 +-- src/auxiliary/linalg.jl | 125 ++--- src/auxiliary/random.jl | 7 +- src/fusiontrees/fusiontrees.jl | 135 ++--- src/fusiontrees/iterator.jl | 60 +- src/fusiontrees/manipulations.jl | 350 ++++++------ src/sectors/anyons.jl | 47 +- src/sectors/fermions.jl | 56 +- src/sectors/groups.jl | 26 +- src/sectors/irreps.jl | 188 ++++--- src/sectors/product.jl | 125 +++-- src/spaces/cartesianspace.jl | 10 +- src/spaces/complexspace.jl | 6 +- src/spaces/deligne.jl | 14 +- src/spaces/generalspace.jl | 5 +- src/spaces/gradedspace.jl | 115 ++-- src/spaces/homspace.jl | 36 +- src/spaces/planarspace.jl | 2 +- src/spaces/productspace.jl | 105 ++-- src/spaces/vectorspaces.jl | 18 +- src/tensors/adjoint.jl | 52 +- src/tensors/braidingtensor.jl | 216 ++++---- src/tensors/factorizations.jl | 177 +++--- src/tensors/linalg.jl | 163 +++--- src/tensors/tensor.jl | 184 ++++--- src/tensors/tensortreeiterator.jl | 22 +- src/tensors/truncation.jl | 95 ++-- src/tensors/vectorinterface.jl | 15 +- test/fusiontrees.jl | 5 +- test/planar.jl | 49 +- test/sectors.jl | 2 +- test/spaces.jl | 2 +- test/tensors.jl | 881 +++++++++++++++--------------- 45 files changed, 2153 insertions(+), 1956 deletions(-) diff --git a/benchmark/benchtools.jl b/benchmark/benchtools.jl index 32a13fcc..7504b85a 100644 --- a/benchmark/benchtools.jl +++ b/benchmark/benchtools.jl @@ -1,82 +1,82 @@ -function distributeZ2(D; p = 0.5) - D0 = ceil(Int, p*D) +function distributeZ2(D; p=0.5) + D0 = ceil(Int, p * D) D1 = D - D0 return [(0, D0), (1, D1)] end -distributeU1(D; p = 0.25) = distributeU1_poisson(D; p = p) +distributeU1(D; p=0.25) = distributeU1_poisson(D; p=p) -function distributeU1_exponential(D; p = 0.25) - λ = (1-p)/(1+p) - D0 = ceil(Int, p*D) - if isodd(D-D0) - D0 = D0 == 1 ? 2 : D0-1 +function distributeU1_exponential(D; p=0.25) + λ = (1 - p) / (1 + p) + D0 = ceil(Int, p * D) + if isodd(D - D0) + D0 = D0 == 1 ? 2 : D0 - 1 end sectors = [(0, D0)] Drem = D - D0 n = 1 while Drem > 0 pn = p * λ^n - Dn = ceil(Int, pn*D) + Dn = ceil(Int, pn * D) sectors = push!(sectors, (n, Dn), (-n, Dn)) - Drem -= 2*Dn + Drem -= 2 * Dn n += 1 end - return sort!(sectors, by = first) + return sort!(sectors; by=first) end -function distributeU1_poisson(D; p = 0.25) - λ = log((1/p+1)/2) - D0 = ceil(Int, p*D) - if isodd(D-D0) - D0 = D0 == 1 ? 2 : D0-1 +function distributeU1_poisson(D; p=0.25) + λ = log((1 / p + 1) / 2) + D0 = ceil(Int, p * D) + if isodd(D - D0) + D0 = D0 == 1 ? 2 : D0 - 1 end sectors = [(0, D0)] Drem = D - D0 n = 1 while Drem > 0 pn = p * λ^n / factorial(n) - Dn = ceil(Int, pn*D) + Dn = ceil(Int, pn * D) sectors = push!(sectors, (n, Dn), (-n, Dn)) - Drem -= 2*Dn + Drem -= 2 * Dn n += 1 end - return sort!(sectors, by = first) + return sort!(sectors; by=first) end module Timers - struct Timer{F,D<:Tuple} - f::F - argsref::Base.RefValue{D} - Timer(f, args...) = new{typeof(f), typeof(args)}(f, Ref(args)) - end +struct Timer{F,D<:Tuple} + f::F + argsref::Base.RefValue{D} + Timer(f, args...) = new{typeof(f),typeof(args)}(f, Ref(args)) +end - @noinline donothing(arg) = arg - function (t::Timer)(; inner = 1, outer = 1) - args = t.argsref[] - f = t.f - f(args...) # run once to compile - times = zeros(Float64, (outer,)) - gctimes = zeros(Float64, (outer,)) - @inbounds for i = 1:outer - if inner == 1 - gcstart = Base.gc_num() - start = Base.time_ns() +@noinline donothing(arg) = arg +function (t::Timer)(; inner=1, outer=1) + args = t.argsref[] + f = t.f + f(args...) # run once to compile + times = zeros(Float64, (outer,)) + gctimes = zeros(Float64, (outer,)) + @inbounds for i in 1:outer + if inner == 1 + gcstart = Base.gc_num() + start = Base.time_ns() + donothing(f(args...)) + stop = Base.time_ns() + gcstop = Base.gc_num() + else + gcstart = Base.gc_num() + start = Base.time_ns() + for _ in 1:inner donothing(f(args...)) - stop = Base.time_ns() - gcstop = Base.gc_num() - else - gcstart = Base.gc_num() - start = Base.time_ns() - for _ = 1:inner - donothing(f(args...)) - end - stop = Base.time_ns() - gcstop = Base.gc_num() end - times[i] = (stop - start)/(1e9)/inner - gctimes[i] = Base.GC_Diff(gcstop, gcstart).total_time/1e9/inner + stop = Base.time_ns() + gcstop = Base.gc_num() end - return times, gctimes + times[i] = (stop - start) / (1e9) / inner + gctimes[i] = Base.GC_Diff(gcstop, gcstart).total_time / 1e9 / inner end + return times, gctimes +end end diff --git a/benchmark/itensors_timers.jl b/benchmark/itensors_timers.jl index 0d075d45..96bebe5b 100644 --- a/benchmark/itensors_timers.jl +++ b/benchmark/itensors_timers.jl @@ -1,85 +1,86 @@ include("benchtools.jl") module ITensorsTimers - using ITensors - import ..Timers +using ITensors +using ..Timers: Timers - function add_indexlabels_as_tags(tensor, labels) - new_inds = map(x -> settags(x[2], "l$(x[1])"), zip(labels, tensor.inds)) - return ITensor(tensor.store, new_inds) - end +function add_indexlabels_as_tags(tensor, labels) + new_inds = map(x -> settags(x[2], "l$(x[1])"), zip(labels, tensor.inds)) + return ITensor(tensor.store, new_inds) +end - function mpo_timer(T = Float64; Vmpo, Vmps, Vphys) - A = randomITensor(T, Vmps, Vphys, dag(Vmps)) - M = randomITensor(T, Vmpo, Vphys, dag(Vphys), dag(Vmpo)) - FL = randomITensor(T, Vmps, dag(Vmpo), dag(Vmps)) - FR = randomITensor(T, Vmps, Vmpo, dag(Vmps)) +function mpo_timer(T=Float64; Vmpo, Vmps, Vphys) + A = randomITensor(T, Vmps, Vphys, dag(Vmps)) + M = randomITensor(T, Vmpo, Vphys, dag(Vphys), dag(Vmpo)) + FL = randomITensor(T, Vmps, dag(Vmpo), dag(Vmps)) + FR = randomITensor(T, Vmps, Vmpo, dag(Vmps)) - FL1 = add_indexlabels_as_tags(FL, (4, 2, 1)) - A1 = add_indexlabels_as_tags(A, (1, 3, 6)) - M1 = add_indexlabels_as_tags(M, (2, 5, 3, 7)) - A2 = add_indexlabels_as_tags(A, (4, 5, 8)) - FR1 = add_indexlabels_as_tags(FR, (6, 7, 8)) + FL1 = add_indexlabels_as_tags(FL, (4, 2, 1)) + A1 = add_indexlabels_as_tags(A, (1, 3, 6)) + M1 = add_indexlabels_as_tags(M, (2, 5, 3, 7)) + A2 = add_indexlabels_as_tags(A, (4, 5, 8)) + FR1 = add_indexlabels_as_tags(FR, (6, 7, 8)) - return Timers.Timer(A1, A2, M1, FL1, FR1) do A1, A2, M1, FL1, FR1 - C = ((((FL1*A1)*M1)*dag(A2))*FR1) - return C[] - end + return Timers.Timer(A1, A2, M1, FL1, FR1) do A1, A2, M1, FL1, FR1 + C = ((((FL1 * A1) * M1) * dag(A2)) * FR1) + return C[] end +end - function pepo_timer(T = Float64; Vpepo, Vpeps, Venv, Vphys) - Vpeps′ = dag(Vpeps) - Vpepo′ = dag(Vpepo) - Venv′ = dag(Venv) - Vphys′ = dag(Vphys) +function pepo_timer(T=Float64; Vpepo, Vpeps, Venv, Vphys) + Vpeps′ = dag(Vpeps) + Vpepo′ = dag(Vpepo) + Venv′ = dag(Venv) + Vphys′ = dag(Vphys) - A = randomITensor(T, Vpeps, Vpeps, Vphys, Vpeps′, Vpeps′) - P = randomITensor(T, Vpepo, Vpepo, Vphys, Vphys′, Vpepo′, Vpepo′) - FL = randomITensor(T, Venv, Vpeps, Vpepo′, Vpeps′, Venv′) - FD = randomITensor(T, Venv, Vpeps, Vpepo′, Vpeps′, Venv′) - FR = randomITensor(T, Venv, Vpeps, Vpepo, Vpeps′, Venv′) - FU = randomITensor(T, Venv, Vpeps, Vpepo, Vpeps′, Venv′) + A = randomITensor(T, Vpeps, Vpeps, Vphys, Vpeps′, Vpeps′) + P = randomITensor(T, Vpepo, Vpepo, Vphys, Vphys′, Vpepo′, Vpepo′) + FL = randomITensor(T, Venv, Vpeps, Vpepo′, Vpeps′, Venv′) + FD = randomITensor(T, Venv, Vpeps, Vpepo′, Vpeps′, Venv′) + FR = randomITensor(T, Venv, Vpeps, Vpepo, Vpeps′, Venv′) + FU = randomITensor(T, Venv, Vpeps, Vpepo, Vpeps′, Venv′) - FL1 = add_indexlabels_as_tags(FL, (18, 7, 4, 2, 1)) - FU1 = add_indexlabels_as_tags(FU, (1, 3, 6, 9, 10)) - A1 = add_indexlabels_as_tags(A, (2, 17, 5, 3, 11)) - P1 = add_indexlabels_as_tags(P, (4, 16, 8, 5, 6, 12)) - A2 = add_indexlabels_as_tags(A, (7, 15, 8, 9, 13)) - FR1 = add_indexlabels_as_tags(FR, (10, 11, 12, 13, 14)) - FD1 = add_indexlabels_as_tags(FD, (14, 15, 16, 17, 18)) + FL1 = add_indexlabels_as_tags(FL, (18, 7, 4, 2, 1)) + FU1 = add_indexlabels_as_tags(FU, (1, 3, 6, 9, 10)) + A1 = add_indexlabels_as_tags(A, (2, 17, 5, 3, 11)) + P1 = add_indexlabels_as_tags(P, (4, 16, 8, 5, 6, 12)) + A2 = add_indexlabels_as_tags(A, (7, 15, 8, 9, 13)) + FR1 = add_indexlabels_as_tags(FR, (10, 11, 12, 13, 14)) + FD1 = add_indexlabels_as_tags(FD, (14, 15, 16, 17, 18)) - return Timers.Timer(A1, A2, P1, FL1, FD1, FR1, FU1) do A1, A2, P1, FL1, FD1, FR1, FU1 - C = (((((FL1*FU1)*A1)*P1)*dag(A2))*FR1)*FD1 - return C[] - end + return Timers.Timer(A1, A2, P1, FL1, FD1, FR1, FU1) do A1, A2, P1, FL1, FD1, FR1, FU1 + C = (((((FL1 * FU1) * A1) * P1) * dag(A2)) * FR1) * FD1 + return C[] end +end - function mera_timer(T = Float64; Vmera) - Vmera′ = dag(Vmera); +function mera_timer(T=Float64; Vmera) + Vmera′ = dag(Vmera) - u = randomITensor(T, Vmera, Vmera, Vmera′, Vmera′) - w = randomITensor(T, Vmera, Vmera, Vmera′) - ρ = randomITensor(T, Vmera, Vmera, Vmera, Vmera′, Vmera′, Vmera′) - h = randomITensor(T, Vmera, Vmera, Vmera, Vmera′, Vmera′, Vmera′) + u = randomITensor(T, Vmera, Vmera, Vmera′, Vmera′) + w = randomITensor(T, Vmera, Vmera, Vmera′) + ρ = randomITensor(T, Vmera, Vmera, Vmera, Vmera′, Vmera′, Vmera′) + h = randomITensor(T, Vmera, Vmera, Vmera, Vmera′, Vmera′, Vmera′) - h1 = add_indexlabels_as_tags(h, (9, 3, 4, 5, 1, 2)) - u1 = add_indexlabels_as_tags(u, (1, 2, 7, 12)) - u2 = add_indexlabels_as_tags(u, (3, 4, 11, 13)) - u3 = add_indexlabels_as_tags(u, (8, 5, 15, 6)) - w1 = add_indexlabels_as_tags(w, (6, 7, 19)) - u4 = add_indexlabels_as_tags(u, (8, 9, 17, 10)) - w2 = add_indexlabels_as_tags(w, (10, 11, 22)) - w3 = add_indexlabels_as_tags(w, (12, 14, 20)) - w4 = add_indexlabels_as_tags(w, (13, 14, 23)) - ρ1 = add_indexlabels_as_tags(ρ, (18, 19, 20, 21, 22, 23)) - w5 = add_indexlabels_as_tags(w, (16, 15, 18)) - w6 = add_indexlabels_as_tags(w, (16, 17, 21)) + h1 = add_indexlabels_as_tags(h, (9, 3, 4, 5, 1, 2)) + u1 = add_indexlabels_as_tags(u, (1, 2, 7, 12)) + u2 = add_indexlabels_as_tags(u, (3, 4, 11, 13)) + u3 = add_indexlabels_as_tags(u, (8, 5, 15, 6)) + w1 = add_indexlabels_as_tags(w, (6, 7, 19)) + u4 = add_indexlabels_as_tags(u, (8, 9, 17, 10)) + w2 = add_indexlabels_as_tags(w, (10, 11, 22)) + w3 = add_indexlabels_as_tags(w, (12, 14, 20)) + w4 = add_indexlabels_as_tags(w, (13, 14, 23)) + ρ1 = add_indexlabels_as_tags(ρ, (18, 19, 20, 21, 22, 23)) + w5 = add_indexlabels_as_tags(w, (16, 15, 18)) + w6 = add_indexlabels_as_tags(w, (16, 17, 21)) - return Timers.Timer(u1, u2, u3, u4, w1, w2, w3, w4, w5, w6, ρ1, h1) do u1, u2, u3, u4, w1, w2, w3, w4, w5, w6, ρ1, h1 - C = (((((( (h1*u1) * dag(u2) ) * - (u3*w1) ) * - (dag(u4)*dag(w2)) ) * - ( (w3 * dag(w4)) * ρ1)) * w5) * dag(w6)) - return C - end + return Timers.Timer(u1, u2, u3, u4, w1, w2, w3, w4, w5, w6, ρ1, + h1) do u1, u2, u3, u4, w1, w2, w3, w4, w5, w6, ρ1, h1 + C = (((((((h1 * u1) * dag(u2)) * + (u3 * w1)) * + (dag(u4) * dag(w2))) * + ((w3 * dag(w4)) * ρ1)) * w5) * dag(w6)) + return C end end +end diff --git a/benchmark/plotmera.jl b/benchmark/plotmera.jl index d3a7f857..8d1d517c 100644 --- a/benchmark/plotmera.jl +++ b/benchmark/plotmera.jl @@ -14,17 +14,18 @@ meraplots = Matrix{Any}(undef, (numplots, 3)) data = ["_mera_triv.txt", "_mera_z2.txt", "_mera_u1.txt"] dims = [meradims_triv, meradims_z2, meradims_u1] name = ["Trivial", "Z2 symmetry", "U1 symmetry"] -for j = 1:3 +for j in 1:3 s = data[j] data_tensorkit = try - readdlm("results/tensorkit"*s) + readdlm("results/tensorkit" * s) catch nothing end ymin = minimum(data_tensorkit) ymax = maximum(data_tensorkit) data_itensors = try - readdlm("results/itensors"*s) - readdlm("results/itensors"*s[1:end-4]*"_gc.txt") + readdlm("results/itensors" * s) - + readdlm("results/itensors" * s[1:(end - 4)] * "_gc.txt") catch nothing end @@ -33,7 +34,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_itensors)) end data_tenpy = try - readdlm("results/tenpy"*s) + readdlm("results/tenpy" * s) catch nothing end @@ -42,7 +43,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_tenpy)) end data_tensornetwork = try - readdlm("results/tensornetwork"*s) + readdlm("results/tensornetwork" * s) catch nothing end @@ -53,31 +54,34 @@ for j = 1:3 ymin = 10^(floor(log10(ymin))) ymax = 10^(ceil(log10(ymax))) - for i = 1:numplots + for i in 1:numplots Dmera = dims[j][1][i] - p = boxplot(data_tensorkit[:,i], label="TensorKit.jl", markersize = M) + p = boxplot(data_tensorkit[:, i]; label="TensorKit.jl", markersize=M) !isnothing(data_itensors) && - boxplot!(p, data_itensors[:,i], label="ITensors.jl", markersize = M) + boxplot!(p, data_itensors[:, i]; label="ITensors.jl", markersize=M) !isnothing(data_tenpy) && - boxplot!(p, data_tenpy[:,i], label="Tenpy", markersize = M) + boxplot!(p, data_tenpy[:, i]; label="Tenpy", markersize=M) !isnothing(data_tensornetwork) && - boxplot!(p, data_tensornetwork[:,i], label="TensorNetwork", markersize = M) - plot!(p, yscale = :log10, yminorticks = true, xticks = [], xgrid = false, xshowaxis = false, ylim = (ymin, ymax)) + boxplot!(p, data_tensornetwork[:, i]; label="TensorNetwork", markersize=M) + plot!(p; yscale=:log10, yminorticks=true, xticks=[], xgrid=false, xshowaxis=false, + ylim=(ymin, ymax)) if i == 1 - plot!(p, yguide = name[j], yguide_position = :left, yguidefontsize = 10, ytickfontsize = 8, left_margin = 6mm) - plot!(p, title = "χ = $Dmera", titlefontsize = 10) + plot!(p; yguide=name[j], yguide_position=:left, yguidefontsize=10, + ytickfontsize=8, left_margin=6mm) + plot!(p; title="χ = $Dmera", titlefontsize=10) else - plot!(p, title = "$Dmera", titlefontsize = 10) - plot!(p, left_margin = -5mm, yformatter = _->"", yshowaxis = false) + plot!(p; title="$Dmera", titlefontsize=10) + plot!(p; left_margin=-5mm, yformatter=_ -> "", yshowaxis=false) end if i == numplots && j == 3 - plot!(p, legendfontsize = 10, legend = :bottomright) + plot!(p; legendfontsize=10, legend=:bottomright) else - plot!(p, legend = false) + plot!(p; legend=false) end meraplots[i, j] = p end end -plotgrid = plot(meraplots..., layout=grid(3,numplots), link = :y, thickness_scaling = 1, size = (1500, 800)) +plotgrid = plot(meraplots...; layout=grid(3, numplots), link=:y, thickness_scaling=1, + size=(1500, 800)) savefig(plotgrid, "meraresults.pdf") diff --git a/benchmark/plotmpo.jl b/benchmark/plotmpo.jl index 60b901c6..ed7a9431 100644 --- a/benchmark/plotmpo.jl +++ b/benchmark/plotmpo.jl @@ -13,17 +13,17 @@ mpoplots = Matrix{Any}(undef, (numplots, 3)) data = ["_mpo_triv.txt", "_mpo_z2.txt", "_mpo_u1.txt"] dims = [mpodims_triv, mpodims_z2, mpodims_u1] name = ["Trivial", "Z2 symmetry", "U1 symmetry"] -for j = 1:3 +for j in 1:3 s = data[j] data_tensorkit = try - readdlm("results/tensorkit"*s) + readdlm("results/tensorkit" * s) catch nothing end ymin = minimum(data_tensorkit) ymax = maximum(data_tensorkit) data_itensors = try - readdlm("results/itensors"*s) + readdlm("results/itensors" * s) catch nothing end @@ -32,7 +32,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_itensors)) end data_tenpy = try - readdlm("results/tenpy"*s) + readdlm("results/tenpy" * s) catch nothing end @@ -41,7 +41,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_tenpy)) end data_tensornetwork = try - readdlm("results/tensornetwork"*s) + readdlm("results/tensornetwork" * s) catch nothing end @@ -52,33 +52,36 @@ for j = 1:3 ymin = 10^(floor(log10(ymin))) ymax = 10^(ceil(log10(ymax))) - for i = 1:numplots + for i in 1:numplots Dmps = dims[j][1][i] Dmpo = dims[j][2][i] Dphys = dims[j][3][i] - p = boxplot(data_tensorkit[:,i], label="TensorKit.jl", markersize = M) + p = boxplot(data_tensorkit[:, i]; label="TensorKit.jl", markersize=M) !isnothing(data_itensors) && - boxplot!(p, data_itensors[:,i], label="ITensors.jl", markersize = M) + boxplot!(p, data_itensors[:, i]; label="ITensors.jl", markersize=M) !isnothing(data_tenpy) && - boxplot!(p, data_tenpy[:,i], label="Tenpy", markersize = M) + boxplot!(p, data_tenpy[:, i]; label="Tenpy", markersize=M) !isnothing(data_tensornetwork) && - boxplot!(p, data_tensornetwork[:,i], label="TensorNetwork", markersize = M) - plot!(p, yscale = :log10, yminorticks = true, xticks = [], xgrid = false, xshowaxis = false, ylim = (ymin, ymax)) + boxplot!(p, data_tensornetwork[:, i]; label="TensorNetwork", markersize=M) + plot!(p; yscale=:log10, yminorticks=true, xticks=[], xgrid=false, xshowaxis=false, + ylim=(ymin, ymax)) if i == 1 - plot!(p, title = "(D, M, d) = ($Dmps, $Dmpo, $Dphys)", titlefontsize = 10) - plot!(p, yguide = name[j], yguide_position = :left, yguidefontsize = 10, ytickfontsize = 8, left_margin = 6mm) + plot!(p; title="(D, M, d) = ($Dmps, $Dmpo, $Dphys)", titlefontsize=10) + plot!(p; yguide=name[j], yguide_position=:left, yguidefontsize=10, + ytickfontsize=8, left_margin=6mm) else - plot!(p, title = "($Dmps, $Dmpo, $Dphys)", titlefontsize = 10) - plot!(p, left_margin = -5mm, yformatter = _->"", yshowaxis = false) + plot!(p; title="($Dmps, $Dmpo, $Dphys)", titlefontsize=10) + plot!(p; left_margin=-5mm, yformatter=_ -> "", yshowaxis=false) end if i == numplots && j == 3 - plot!(p, legendfontsize = 10, legend = :bottomright) + plot!(p; legendfontsize=10, legend=:bottomright) else - plot!(p, legend = false) + plot!(p; legend=false) end mpoplots[i, j] = p end end -plotgrid = plot(mpoplots..., layout=grid(3,numplots), link = :y, thickness_scaling = 1, size = (1500, 800)) +plotgrid = plot(mpoplots...; layout=grid(3, numplots), link=:y, thickness_scaling=1, + size=(1500, 800)) savefig(plotgrid, "mporesults.pdf") diff --git a/benchmark/plotpepo.jl b/benchmark/plotpepo.jl index 5c27f272..40ce5116 100644 --- a/benchmark/plotpepo.jl +++ b/benchmark/plotpepo.jl @@ -12,17 +12,17 @@ pepoplots = Matrix{Any}(undef, (numplots, 3)) data = ["_pepo_triv.txt", "_pepo_z2.txt", "_pepo_u1.txt"] dims = [pepodims_triv, pepodims_z2, pepodims_u1] name = ["Trivial", "Z2 symmetry", "U1 symmetry"] -for j = 1:3 +for j in 1:3 s = data[j] data_tensorkit = try - readdlm("results/tensorkit"*s) + readdlm("results/tensorkit" * s) catch nothing end ymin = minimum(data_tensorkit) ymax = maximum(data_tensorkit) data_itensors = try - readdlm("results/itensors"*s) + readdlm("results/itensors" * s) catch nothing end @@ -31,7 +31,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_itensors)) end data_tenpy = try - readdlm("results/tenpy"*s) + readdlm("results/tenpy" * s) catch nothing end @@ -40,7 +40,7 @@ for j = 1:3 ymax = max(ymax, maximum(data_tenpy)) end data_tensornetwork = try - readdlm("results/tensornetwork"*s) + readdlm("results/tensornetwork" * s) catch nothing end @@ -51,34 +51,38 @@ for j = 1:3 ymin = 10^(floor(log10(ymin))) ymax = 10^(ceil(log10(ymax))) - for i = 1:numplots + for i in 1:numplots Dpeps = dims[j][1][i] Dpepo = dims[j][2][i] Dphys = dims[j][3][i] Denv = dims[j][4][i] - p = boxplot(data_tensorkit[:,i], label="TensorKit.jl", markersize = M) + p = boxplot(data_tensorkit[:, i]; label="TensorKit.jl", markersize=M) !isnothing(data_itensors) && - boxplot!(p, data_itensors[:,i], label="ITensors.jl", markersize = M) + boxplot!(p, data_itensors[:, i]; label="ITensors.jl", markersize=M) !isnothing(data_tenpy) && - boxplot!(p, data_tenpy[:,i], label="Tenpy", markersize = M) + boxplot!(p, data_tenpy[:, i]; label="Tenpy", markersize=M) !isnothing(data_tensornetwork) && - boxplot!(p, data_tensornetwork[:,i], label="TensorNetwork", markersize = M) - plot!(p, yscale = :log10, yminorticks = true, xticks = [], xgrid = false, xshowaxis = false, ylim = (ymin, ymax)) + boxplot!(p, data_tensornetwork[:, i]; label="TensorNetwork", markersize=M) + plot!(p; yscale=:log10, yminorticks=true, xticks=[], xgrid=false, xshowaxis=false, + ylim=(ymin, ymax)) if i == 1 - plot!(p, title = "(D, M, d, χ) = ($Dpeps, $Dpepo, $Dphys, $Denv)", titlefontsize = 10) - plot!(p, yguide = name[j], yguide_position = :left, yguidefontsize = 10, ytickfontsize = 8, left_margin = 6mm) + plot!(p; title="(D, M, d, χ) = ($Dpeps, $Dpepo, $Dphys, $Denv)", + titlefontsize=10) + plot!(p; yguide=name[j], yguide_position=:left, yguidefontsize=10, + ytickfontsize=8, left_margin=6mm) else - plot!(p, title = "($Dpeps, $Dpepo, $Dphys, $Denv)", titlefontsize = 10) - plot!(p, left_margin = -5mm, yformatter = _->"", yshowaxis = false) + plot!(p; title="($Dpeps, $Dpepo, $Dphys, $Denv)", titlefontsize=10) + plot!(p; left_margin=-5mm, yformatter=_ -> "", yshowaxis=false) end if i == numplots && j == 3 - plot!(p, legendfontsize = 10, legend = :bottomright) + plot!(p; legendfontsize=10, legend=:bottomright) else - plot!(p, legend = false) + plot!(p; legend=false) end pepoplots[i, j] = p end end -plotgrid = plot(pepoplots..., layout=grid(3,numplots), link = :y, thickness_scaling = 1, size = (1500, 800)) +plotgrid = plot(pepoplots...; layout=grid(3, numplots), link=:y, thickness_scaling=1, + size=(1500, 800)) savefig(plotgrid, "peporesults.pdf") diff --git a/benchmark/run_itensors.jl b/benchmark/run_itensors.jl index 24d27154..740325d8 100644 --- a/benchmark/run_itensors.jl +++ b/benchmark/run_itensors.jl @@ -1,10 +1,10 @@ include("bonds.jl") include("itensors_timers.jl") -import LinearAlgebra +using LinearAlgebra: LinearAlgebra LinearAlgebra.BLAS.set_num_threads(1) -import ITensors +using ITensors: ITensors ITensors.disable_combine_contract() #ITensors.enable_combine_contract() using ITensors: Index, QN @@ -18,7 +18,7 @@ K = 100 # number of repititions; scale up for more reliable testing. N = length(mpodims_triv[1]) # number of different cases mpo_triv_times = zeros(Float64, (K, N)) mpo_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = mpodims_triv[1][i] Dmpo = mpodims_triv[2][i] Dphys = mpodims_triv[3][i] @@ -27,13 +27,13 @@ for i = 1:N Vmpo = Index(Dmpo) Vphys = Index(Dphys) - mpo_timer = ITensorsTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = ITensorsTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_triv_times[:, i] = times mpo_triv_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -45,22 +45,22 @@ writedlm("results/itensors_mpo_triv_gc.txt", mpo_triv_times_gc) N = length(mpodims_z2[1]) # number of different cases mpo_z2_times = zeros(Float64, (K, N)) mpo_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = distributeZ2(mpodims_z2[1][i]) Dmpo = distributeZ2(mpodims_z2[2][i]) Dphys = distributeZ2(mpodims_z2[3][i]) - Vmps = Index((QN(a,2)=>b for (a,b) in Dmps)...) - Vmpo = Index((QN(a,2)=>b for (a,b) in Dmpo)...) - Vphys = Index((QN(a,2)=>b for (a,b) in Dphys)...) + Vmps = Index((QN(a, 2) => b for (a, b) in Dmps)...) + Vmpo = Index((QN(a, 2) => b for (a, b) in Dmpo)...) + Vphys = Index((QN(a, 2) => b for (a, b) in Dphys)...) - mpo_timer = ITensorsTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = ITensorsTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_z2_times[:, i] = times mpo_z2_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -72,22 +72,22 @@ writedlm("results/itensors_mpo_z2_gc.txt", mpo_z2_times_gc) N = length(mpodims_u1[1]) # number of different cases mpo_u1_times = zeros(Float64, (K, N)) mpo_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = distributeU1(mpodims_u1[1][i]) Dmpo = distributeU1(mpodims_u1[2][i]) Dphys = distributeU1(mpodims_u1[3][i]) - Vmps = Index((QN(a)=>b for (a,b) in Dmps)...) - Vmpo = Index((QN(a)=>b for (a,b) in Dmpo)...) - Vphys = Index((QN(a)=>b for (a,b) in Dphys)...) + Vmps = Index((QN(a) => b for (a, b) in Dmps)...) + Vmpo = Index((QN(a) => b for (a, b) in Dmpo)...) + Vphys = Index((QN(a) => b for (a, b) in Dphys)...) - mpo_timer = ITensorsTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = ITensorsTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_u1_times[:, i] = times mpo_u1_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end @@ -101,7 +101,7 @@ writedlm("results/itensors_mpo_u1_gc.txt", mpo_u1_times_gc) N = length(pepodims_triv[1]) # number of different cases pepo_triv_times = zeros(Float64, (K, N)) pepo_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = pepodims_triv[1][i] Dpepo = pepodims_triv[2][i] Dphys = pepodims_triv[3][i] @@ -112,13 +112,14 @@ for i = 1:N Vphys = Index(Dphys) Venv = Index(Denv) - pepo_timer = ITensorsTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = ITensorsTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_triv_times[:, i] = times pepo_triv_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -130,24 +131,25 @@ writedlm("results/itensors_pepo_triv_gc.txt", pepo_triv_times_gc) N = length(pepodims_z2[1]) # number of different cases pepo_z2_times = zeros(Float64, (K, N)) pepo_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = distributeZ2(pepodims_z2[1][i]) Dpepo = distributeZ2(pepodims_z2[2][i]) Dphys = distributeZ2(pepodims_z2[3][i]) Denv = distributeZ2(pepodims_z2[4][i]) - Vpeps = Index((QN(a,2)=>b for (a,b) in Dpeps)...) - Vpepo = Index((QN(a,2)=>b for (a,b) in Dpepo)...) - Vphys = Index((QN(a,2)=>b for (a,b) in Dphys)...) - Venv = Index((QN(a,2)=>b for (a,b) in Denv)...) + Vpeps = Index((QN(a, 2) => b for (a, b) in Dpeps)...) + Vpepo = Index((QN(a, 2) => b for (a, b) in Dpepo)...) + Vphys = Index((QN(a, 2) => b for (a, b) in Dphys)...) + Venv = Index((QN(a, 2) => b for (a, b) in Denv)...) - pepo_timer = ITensorsTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = ITensorsTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_z2_times[:, i] = times pepo_z2_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -159,24 +161,25 @@ writedlm("results/itensors_pepo_z2_gc.txt", pepo_z2_times_gc) N = length(pepodims_u1[1]) # number of different cases pepo_u1_times = zeros(Float64, (K, N)) pepo_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = distributeU1(pepodims_u1[1][i]) Dpepo = distributeU1(pepodims_u1[2][i]) Dphys = distributeU1(pepodims_u1[3][i]) Denv = distributeU1(pepodims_u1[4][i]) - Vpeps = Index((QN(a)=>b for (a,b) in Dpeps)...) - Vpepo = Index((QN(a)=>b for (a,b) in Dpepo)...) - Vphys = Index((QN(a)=>b for (a,b) in Dphys)...) - Venv = Index((QN(a)=>b for (a,b) in Denv)...) + Vpeps = Index((QN(a) => b for (a, b) in Dpeps)...) + Vpepo = Index((QN(a) => b for (a, b) in Dpepo)...) + Vphys = Index((QN(a) => b for (a, b) in Dphys)...) + Venv = Index((QN(a) => b for (a, b) in Denv)...) - pepo_timer = ITensorsTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = ITensorsTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_u1_times[:, i] = times pepo_u1_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end @@ -190,19 +193,19 @@ writedlm("results/itensors_pepo_u1_gc.txt", pepo_u1_times_gc) N = length(meradims_triv[1]) # number of different cases mera_triv_times = zeros(Float64, (K, N)) mera_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = meradims_triv[1][i] Vmera = Index(Dmera) - mera_timer = ITensorsTimers.mera_timer(; Vmera = Vmera) + mera_timer = ITensorsTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_triv_times[:, i] = times mera_triv_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -214,19 +217,19 @@ writedlm("results/itensors_mera_triv_gc.txt", mera_triv_times_gc) N = length(meradims_z2[1]) # number of different cases mera_z2_times = zeros(Float64, (K, N)) mera_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = distributeZ2(meradims_z2[1][i]) - Vmera = Index((QN(a,2)=>b for (a,b) in Dmera)...) + Vmera = Index((QN(a, 2) => b for (a, b) in Dmera)...) - mera_timer = ITensorsTimers.mera_timer(; Vmera = Vmera) + mera_timer = ITensorsTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_z2_times[:, i] = times mera_z2_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -238,19 +241,19 @@ writedlm("results/itensors_mera_z2_gc.txt", mera_z2_times_gc) N = length(meradims_u1[1]) # number of different cases mera_u1_times = zeros(Float64, (K, N)) mera_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = distributeU1(meradims_u1[1][i]) - Vmera = Index((QN(a)=>b for (a,b) in Dmera)...) + Vmera = Index((QN(a) => b for (a, b) in Dmera)...) - mera_timer = ITensorsTimers.mera_timer(; Vmera = Vmera) + mera_timer = ITensorsTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_u1_times[:, i] = times mera_u1_times_gc[:, i] = times_gc - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end diff --git a/benchmark/run_tensorkit.jl b/benchmark/run_tensorkit.jl index badb0b76..430e2e92 100644 --- a/benchmark/run_tensorkit.jl +++ b/benchmark/run_tensorkit.jl @@ -1,12 +1,12 @@ include("bonds.jl") include("tensorkit_timers.jl") -import LinearAlgebra +using LinearAlgebra: LinearAlgebra LinearAlgebra.BLAS.set_num_threads(1) -import TensorOperations -TensorOperations.enable_cache(; maxrelsize = 0.7) -import TensorKit +using TensorOperations: TensorOperations +TensorOperations.enable_cache(; maxrelsize=0.7) +using TensorKit: TensorKit using TensorKit: ℂ, Z2Space, U1Space using DelimitedFiles @@ -18,7 +18,7 @@ K = 100 # number of repititions; scale up for more reliable testing. N = length(mpodims_triv[1]) # number of different cases mpo_triv_times = zeros(Float64, (K, N)) mpo_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = mpodims_triv[1][i] Dmpo = mpodims_triv[2][i] Dphys = mpodims_triv[3][i] @@ -27,14 +27,14 @@ for i = 1:N Vmpo = ℂ^Dmpo Vphys = ℂ^Dphys - mpo_timer = TensorKitTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = TensorKitTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_triv_times[:, i] = times mpo_triv_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -46,23 +46,23 @@ writedlm("results/tensorkit_mpo_triv_gc.txt", mpo_triv_times_gc) N = length(mpodims_z2[1]) # number of different cases mpo_z2_times = zeros(Float64, (K, N)) mpo_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = distributeZ2(mpodims_z2[1][i]) Dmpo = distributeZ2(mpodims_z2[2][i]) Dphys = distributeZ2(mpodims_z2[3][i]) - Vmps = Z2Space((a=>b for (a,b) in Dmps)...) - Vmpo = Z2Space((a=>b for (a,b) in Dmpo)...) - Vphys = Z2Space((a=>b for (a,b) in Dphys)...) + Vmps = Z2Space((a => b for (a, b) in Dmps)...) + Vmpo = Z2Space((a => b for (a, b) in Dmpo)...) + Vphys = Z2Space((a => b for (a, b) in Dphys)...) - mpo_timer = TensorKitTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = TensorKitTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_z2_times[:, i] = times mpo_z2_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -74,23 +74,23 @@ writedlm("results/tensorkit_mpo_z2_gc.txt", mpo_z2_times_gc) N = length(mpodims_u1[1]) # number of different cases mpo_u1_times = zeros(Float64, (K, N)) mpo_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmps = distributeU1(mpodims_u1[1][i]) Dmpo = distributeU1(mpodims_u1[2][i]) Dphys = distributeU1(mpodims_u1[3][i]) - Vmps = U1Space((a=>b for (a,b) in Dmps)...) - Vmpo = U1Space((a=>b for (a,b) in Dmpo)...) - Vphys = U1Space((a=>b for (a,b) in Dphys)...) + Vmps = U1Space((a => b for (a, b) in Dmps)...) + Vmpo = U1Space((a => b for (a, b) in Dmpo)...) + Vphys = U1Space((a => b for (a, b) in Dphys)...) - mpo_timer = TensorKitTimers.mpo_timer(; Vmps = Vmps, Vmpo = Vmpo, Vphys = Vphys) + mpo_timer = TensorKitTimers.mpo_timer(; Vmps=Vmps, Vmpo=Vmpo, Vphys=Vphys) - times, times_gc = mpo_timer(; outer = K) + times, times_gc = mpo_timer(; outer=K) mpo_u1_times[:, i] = times mpo_u1_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MPO Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end @@ -104,7 +104,7 @@ writedlm("results/tensorkit_mpo_u1_gc.txt", mpo_u1_times_gc) N = length(pepodims_triv[1]) # number of different cases pepo_triv_times = zeros(Float64, (K, N)) pepo_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = pepodims_triv[1][i] Dpepo = pepodims_triv[2][i] Dphys = pepodims_triv[3][i] @@ -115,14 +115,15 @@ for i = 1:N Vphys = ℂ^Dphys Venv = ℂ^Denv - pepo_timer = TensorKitTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = TensorKitTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_triv_times[:, i] = times pepo_triv_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -134,25 +135,26 @@ writedlm("results/tensorkit_pepo_triv_gc.txt", pepo_triv_times_gc) N = length(pepodims_z2[1]) # number of different cases pepo_z2_times = zeros(Float64, (K, N)) pepo_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = distributeZ2(pepodims_z2[1][i]) Dpepo = distributeZ2(pepodims_z2[2][i]) Dphys = distributeZ2(pepodims_z2[3][i]) Denv = distributeZ2(pepodims_z2[4][i]) - Vpeps = Z2Space((a=>b for (a,b) in Dpeps)...) - Vpepo = Z2Space((a=>b for (a,b) in Dpepo)...) - Vphys = Z2Space((a=>b for (a,b) in Dphys)...) - Venv = Z2Space((a=>b for (a,b) in Denv)...) + Vpeps = Z2Space((a => b for (a, b) in Dpeps)...) + Vpepo = Z2Space((a => b for (a, b) in Dpepo)...) + Vphys = Z2Space((a => b for (a, b) in Dphys)...) + Venv = Z2Space((a => b for (a, b) in Denv)...) - pepo_timer = TensorKitTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = TensorKitTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_z2_times[:, i] = times pepo_z2_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -164,25 +166,26 @@ writedlm("results/tensorkit_pepo_z2_gc.txt", pepo_z2_times_gc) N = length(pepodims_u1[1]) # number of different cases pepo_u1_times = zeros(Float64, (K, N)) pepo_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dpeps = distributeU1(pepodims_u1[1][i]) Dpepo = distributeU1(pepodims_u1[2][i]) Dphys = distributeU1(pepodims_u1[3][i]) Denv = distributeU1(pepodims_u1[4][i]) - Vpeps = U1Space((a=>b for (a,b) in Dpeps)...) - Vpepo = U1Space((a=>b for (a,b) in Dpepo)...) - Vphys = U1Space((a=>b for (a,b) in Dphys)...) - Venv = U1Space((a=>b for (a,b) in Denv)...) + Vpeps = U1Space((a => b for (a, b) in Dpeps)...) + Vpepo = U1Space((a => b for (a, b) in Dpepo)...) + Vphys = U1Space((a => b for (a, b) in Dphys)...) + Venv = U1Space((a => b for (a, b) in Denv)...) - pepo_timer = TensorKitTimers.pepo_timer(; Vpeps = Vpeps, Vpepo = Vpepo, Vphys = Vphys, Venv = Venv) + pepo_timer = TensorKitTimers.pepo_timer(; Vpeps=Vpeps, Vpepo=Vpepo, Vphys=Vphys, + Venv=Venv) - times, times_gc = pepo_timer(; outer = K) + times, times_gc = pepo_timer(; outer=K) pepo_u1_times[:, i] = times pepo_u1_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("PEPO Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end @@ -196,20 +199,20 @@ writedlm("results/tensorkit_pepo_u1_gc.txt", pepo_u1_times_gc) N = length(meradims_triv[1]) # number of different cases mera_triv_times = zeros(Float64, (K, N)) mera_triv_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = meradims_triv[1][i] Vmera = ℂ^Dmera - mera_timer = TensorKitTimers.mera_timer(; Vmera = Vmera) + mera_timer = TensorKitTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_triv_times[:, i] = times mera_triv_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, trivial symmetry: case $i out of $N finished at average time $tavg") end @@ -221,20 +224,20 @@ writedlm("results/tensorkit_mera_triv_gc.txt", mera_triv_times_gc) N = length(meradims_z2[1]) # number of different cases mera_z2_times = zeros(Float64, (K, N)) mera_z2_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = distributeZ2(meradims_z2[1][i]) - Vmera = Z2Space((a=>b for (a,b) in Dmera)...) + Vmera = Z2Space((a => b for (a, b) in Dmera)...) - mera_timer = TensorKitTimers.mera_timer(; Vmera = Vmera) + mera_timer = TensorKitTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_z2_times[:, i] = times mera_z2_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, Z2 symmetry: case $i out of $N finished at average time $tavg") end @@ -246,20 +249,20 @@ writedlm("results/tensorkit_mera_z2_gc.txt", mera_z2_times_gc) N = length(meradims_u1[1]) # number of different cases mera_u1_times = zeros(Float64, (K, N)) mera_u1_times_gc = zeros(Float64, (K, N)) -for i = 1:N +for i in 1:N Dmera = distributeU1(meradims_u1[1][i]) - Vmera = U1Space((a=>b for (a,b) in Dmera)...) + Vmera = U1Space((a => b for (a, b) in Dmera)...) - mera_timer = TensorKitTimers.mera_timer(; Vmera = Vmera) + mera_timer = TensorKitTimers.mera_timer(; Vmera=Vmera) - times, times_gc = mera_timer(; outer = K) + times, times_gc = mera_timer(; outer=K) mera_u1_times[:, i] = times mera_u1_times_gc[:, i] = times_gc empty!(TensorOperations.cache) - tavg = sum(times)/K + tavg = sum(times) / K println("MERA Contraction, U1 symmetry: case $i out of $N finished at average time $tavg") end diff --git a/benchmark/tensorkit_timers.jl b/benchmark/tensorkit_timers.jl index 0a1f9ac2..020783c3 100644 --- a/benchmark/tensorkit_timers.jl +++ b/benchmark/tensorkit_timers.jl @@ -1,46 +1,48 @@ include("benchtools.jl") module TensorKitTimers - using TensorKit - import ..Timers +using TensorKit +using ..Timers: Timers - function mpo_timer(f = randn, T = Float64; Vmpo, Vmps, Vphys) - A = Tensor(f, T, Vmps ⊗ Vphys ⊗ Vmps') - M = Tensor(f, T, Vmpo ⊗ Vphys ⊗ Vphys' ⊗ Vmpo') - FL = Tensor(f, T, Vmps ⊗ Vmpo' ⊗ Vmps') - FR = Tensor(f, T, Vmps ⊗ Vmpo ⊗ Vmps') +function mpo_timer(f=randn, T=Float64; Vmpo, Vmps, Vphys) + A = Tensor(f, T, Vmps ⊗ Vphys ⊗ Vmps') + M = Tensor(f, T, Vmpo ⊗ Vphys ⊗ Vphys' ⊗ Vmpo') + FL = Tensor(f, T, Vmps ⊗ Vmpo' ⊗ Vmps') + FR = Tensor(f, T, Vmps ⊗ Vmpo ⊗ Vmps') - return Timers.Timer(A, M, FL, FR) do A, M, FL, FR - @tensor C = FL[4,2,1]*A[1,3,6]*M[2,5,3,7]*conj(A[4,5,8])*FR[6,7,8] - return C - end + return Timers.Timer(A, M, FL, FR) do A, M, FL, FR + @tensor C = FL[4, 2, 1] * A[1, 3, 6] * M[2, 5, 3, 7] * conj(A[4, 5, 8]) * + FR[6, 7, 8] + return C end +end - function pepo_timer(f = randn, T = Float64; Vpepo, Vpeps, Venv, Vphys) - A = Tensor(f, T, Vpeps ⊗ Vpeps ⊗ Vphys ⊗ Vpeps' ⊗ Vpeps') - P = Tensor(f, T, Vpepo ⊗ Vpepo ⊗ Vphys ⊗ Vphys' ⊗ Vpepo' ⊗ Vpepo') - FL = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo' ⊗ Vpeps' ⊗ Venv') - FD = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo' ⊗ Vpeps' ⊗ Venv') - FR = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo ⊗ Vpeps' ⊗ Venv') - FU = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo ⊗ Vpeps' ⊗ Venv') - return Timers.Timer(A, P, FL, FD, FR, FU) do A, P, FL, FD, FR, FU - @tensor C = FL[18,7,4,2,1]*FU[1,3,6,9,10]* - A[2,17,5,3,11]*P[4,16,8,5,6,12]*conj(A[7,15,8,9,13])* - FR[10,11,12,13,14]*FD[14,15,16,17,18] - return C - end +function pepo_timer(f=randn, T=Float64; Vpepo, Vpeps, Venv, Vphys) + A = Tensor(f, T, Vpeps ⊗ Vpeps ⊗ Vphys ⊗ Vpeps' ⊗ Vpeps') + P = Tensor(f, T, Vpepo ⊗ Vpepo ⊗ Vphys ⊗ Vphys' ⊗ Vpepo' ⊗ Vpepo') + FL = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo' ⊗ Vpeps' ⊗ Venv') + FD = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo' ⊗ Vpeps' ⊗ Venv') + FR = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo ⊗ Vpeps' ⊗ Venv') + FU = Tensor(f, T, Venv ⊗ Vpeps ⊗ Vpepo ⊗ Vpeps' ⊗ Venv') + return Timers.Timer(A, P, FL, FD, FR, FU) do A, P, FL, FD, FR, FU + @tensor C = FL[18, 7, 4, 2, 1] * FU[1, 3, 6, 9, 10] * + A[2, 17, 5, 3, 11] * P[4, 16, 8, 5, 6, 12] * conj(A[7, 15, 8, 9, 13]) * + FR[10, 11, 12, 13, 14] * FD[14, 15, 16, 17, 18] + return C end +end - function mera_timer(f = randn, T = Float64; Vmera) - u = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera') - w = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera') - ρ = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera' ⊗ Vmera') - h = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera' ⊗ Vmera') - return Timers.Timer(u, w, ρ, h) do u, w, ρ, h - @tensor C = (((((( (h[9,3,4,5,1,2]*u[1,2,7,12]) * conj(u[3,4,11,13]) ) * - (u[8,5,15,6]*w[6,7,19]) ) * - (conj(u[8,9,17,10])*conj(w[10,11,22])) ) * - ( (w[12,14,20] * conj(w[13,14,23])) * ρ[18,19,20,21,22,23])) * w[16,15,18]) * conj(w[16,17,21])) - return C - end +function mera_timer(f=randn, T=Float64; Vmera) + u = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera') + w = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera') + ρ = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera' ⊗ Vmera') + h = Tensor(f, T, Vmera ⊗ Vmera ⊗ Vmera ⊗ Vmera' ⊗ Vmera' ⊗ Vmera') + return Timers.Timer(u, w, ρ, h) do u, w, ρ, h + @tensor C = (((((((h[9, 3, 4, 5, 1, 2] * u[1, 2, 7, 12]) * conj(u[3, 4, 11, 13])) * + (u[8, 5, 15, 6] * w[6, 7, 19])) * + (conj(u[8, 9, 17, 10]) * conj(w[10, 11, 22]))) * + ((w[12, 14, 20] * conj(w[13, 14, 23])) * ρ[18, 19, 20, 21, 22, 23])) * + w[16, 15, 18]) * conj(w[16, 17, 21])) + return C end end +end diff --git a/docs/make.jl b/docs/make.jl index 9eab41dd..d1cec878 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,17 +1,15 @@ using Documenter using TensorKit -makedocs(modules=[TensorKit], - sitename="TensorKit.jl", - authors = "Jutho Haegeman", - format = Documenter.HTML(; prettyurls = get(ENV, "CI", nothing) == "true", - mathengine = MathJax()), - pages = [ - "Home" => "index.md", +makedocs(; modules=[TensorKit], + sitename="TensorKit.jl", + authors="Jutho Haegeman", + format=Documenter.HTML(; prettyurls=get(ENV, "CI", nothing) == "true", + mathengine=MathJax()), + pages=["Home" => "index.md", "Manual" => ["man/intro.md", "man/tutorial.md", "man/categories.md", - "man/spaces.md", "man/sectors.md", "man/tensors.md"], - "Library" => ["lib/sectors.md","lib/spaces.md","lib/tensors.md"], - "Index" => ["index/index.md"] - ]) + "man/spaces.md", "man/sectors.md", "man/tensors.md"], + "Library" => ["lib/sectors.md", "lib/spaces.md", "lib/tensors.md"], + "Index" => ["index/index.md"]]) -deploydocs(repo = "github.com/Jutho/TensorKit.jl.git") +deploydocs(; repo="github.com/Jutho/TensorKit.jl.git") diff --git a/src/TensorKit.jl b/src/TensorKit.jl index c2786f50..944ade4f 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -10,7 +10,7 @@ module TensorKit # Types: export Sector, AbstractIrrep, Irrep export FusionStyle, UniqueFusion, MultipleFusion, MultiplicityFreeFusion, - SimpleFusion, GenericFusion + SimpleFusion, GenericFusion export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic export Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep export Fermion, FermionParity, FermionNumber, FermionSpin @@ -36,7 +36,7 @@ export infimum, supremum, isisomorphic, ismonomorphic, isepimorphic # methods for sectors and properties thereof export sectortype, sectors, hassector, Nsymbol, Fsymbol, Rsymbol, Bsymbol, - frobeniusschur, twist + frobeniusschur, twist export fusiontrees, braid, permute, transpose export Trivial, ZNSpace, SU2Irrep, U1Irrep, CU1Irrep # Fermion # other fusion tree manipulations, should not be exported: @@ -68,14 +68,14 @@ export inner, dot, norm, normalize, normalize!, tr # factorizations export mul!, lmul!, rmul!, adjoint!, pinv, axpy!, axpby! export leftorth, rightorth, leftnull, rightnull, - leftorth!, rightorth!, leftnull!, rightnull!, - tsvd!, tsvd, eigen, eigen!, eig, eig!, eigh, eigh!, exp, exp!, - isposdef, isposdef!, ishermitian, sylvester + leftorth!, rightorth!, leftnull!, rightnull!, + tsvd!, tsvd, eigen, eigen!, eig, eig!, eigh, eigh!, exp, exp!, + isposdef, isposdef!, ishermitian, sylvester export braid!, permute!, transpose!, twist! export catdomain, catcodomain export OrthogonalFactorizationAlgorithm, QR, QRpos, QL, QLpos, LQ, LQpos, RQ, RQpos, - SVD, SDD, Polar + SVD, SDD, Polar # tensor operations export @tensor, @tensoropt, @ncon, ncon, @planar, @plansor @@ -107,14 +107,14 @@ using Base: @boundscheck, @propagate_inbounds, OneTo, tail, front, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype using Base.Iterators: product, filter -import LinearAlgebra +using LinearAlgebra: LinearAlgebra using LinearAlgebra: norm, dot, normalize, normalize!, tr, - axpy!, axpby!, lmul!, rmul!, mul!, - adjoint, adjoint!, transpose, transpose!, - pinv, sylvester, - eigen, eigen!, svd, svd!, - isposdef, isposdef!, ishermitian, - Diagonal, Hermitian + axpy!, axpby!, lmul!, rmul!, mul!, + adjoint, adjoint!, transpose, transpose!, + pinv, sylvester, + eigen, eigen!, svd, svd!, + isposdef, isposdef!, ishermitian, + Diagonal, Hermitian import Base.Meta # const IndexTuple{N} = NTuple{N, Int} @@ -129,8 +129,8 @@ include("auxiliary/random.jl") #-------------------------------------------------------------------- # experiment with different dictionaries -const SectorDict{K, V} = SortedVectorDict{K, V} -const FusionTreeDict{K, V} = Dict{K, V} +const SectorDict{K,V} = SortedVectorDict{K,V} +const FusionTreeDict{K,V} = Dict{K,V} #-------------------------------------------------------------------- # Exception types: @@ -138,26 +138,26 @@ const FusionTreeDict{K, V} = Dict{K, V} abstract type TensorException <: Exception end # Exception type for all errors related to sector mismatch -struct SectorMismatch{S<:Union{Nothing, String}} <: TensorException +struct SectorMismatch{S<:Union{Nothing,String}} <: TensorException message::S end -SectorMismatch()=SectorMismatch{Nothing}(nothing) +SectorMismatch() = SectorMismatch{Nothing}(nothing) Base.show(io::IO, ::SectorMismatch{Nothing}) = print(io, "SectorMismatch()") Base.show(io::IO, e::SectorMismatch) = print(io, "SectorMismatch(\"", e.message, "\")") # Exception type for all errors related to vector space mismatch -struct SpaceMismatch{S<:Union{Nothing, String}} <: TensorException +struct SpaceMismatch{S<:Union{Nothing,String}} <: TensorException message::S end -SpaceMismatch()=SpaceMismatch{Nothing}(nothing) +SpaceMismatch() = SpaceMismatch{Nothing}(nothing) Base.show(io::IO, ::SpaceMismatch{Nothing}) = print(io, "SpaceMismatch()") Base.show(io::IO, e::SpaceMismatch) = print(io, "SpaceMismatch(\"", e.message, "\")") # Exception type for all errors related to invalid tensor index specification. -struct IndexError{S<:Union{Nothing, String}} <: TensorException +struct IndexError{S<:Union{Nothing,String}} <: TensorException message::S end -IndexError()=IndexError{Nothing}(nothing) +IndexError() = IndexError{Nothing}(nothing) Base.show(io::IO, ::IndexError{Nothing}) = print(io, "IndexError()") Base.show(io::IO, e::IndexError) = print(io, "IndexError(", e.message, ")") diff --git a/src/auxiliary/auxiliary.jl b/src/auxiliary/auxiliary.jl index 38c20207..f407f968 100644 --- a/src/auxiliary/auxiliary.jl +++ b/src/auxiliary/auxiliary.jl @@ -1,10 +1,10 @@ -function linearizepermutation(p1::NTuple{N₁, Int}, p2::NTuple{N₂}, - n₁::Int, n₂::Int) where {N₁, N₂} +function linearizepermutation(p1::NTuple{N₁,Int}, p2::NTuple{N₂}, + n₁::Int, n₂::Int) where {N₁,N₂} p1′ = ntuple(Val(N₁)) do n - p1[n] > n₁ ? n₂+2n₁+1-p1[n] : p1[n] + return p1[n] > n₁ ? n₂ + 2n₁ + 1 - p1[n] : p1[n] end p2′ = ntuple(Val(N₂)) do n - p2[N₂+1-n] > n₁ ? n₂+2n₁+1-p2[N₂+1-n] : p2[N₂+1-n] + return p2[N₂ + 1 - n] > n₁ ? n₂ + 2n₁ + 1 - p2[N₂ + 1 - n] : p2[N₂ + 1 - n] end return (p1′..., p2′...) end @@ -14,9 +14,9 @@ function permutation2swaps(perm) @assert isperm(p) swaps = Vector{Int}() N = length(p) - for k = 1:N-1 - append!(swaps, p[k]-1:-1:k) - for l = k+1:N + for k in 1:(N - 1) + append!(swaps, (p[k] - 1):-1:k) + for l in (k + 1):N if p[l] < p[k] p[l] += 1 end @@ -35,7 +35,7 @@ function _kron(A, B) for IA in eachindex(IndexCartesian(), A) for IB in eachindex(IndexCartesian(), B) I = CartesianIndex(IB.I .+ (IA.I .- 1) .* sB) - C[I] = A[IA]*B[IB] + C[I] = A[IA] * B[IB] end end return C diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index 37569f53..b9c5246d 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -2,6 +2,8 @@ import Base: eltype, transpose @deprecate eltype(T::Type{<:AbstractTensorMap}) scalartype(T) @deprecate eltype(t::AbstractTensorMap) scalartype(t) +#! format: off @deprecate permute(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) permute(t, (p1, p2); copy=copy) @deprecate transpose(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) transpose(t, (p1, p2); copy=copy) @deprecate braid(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple, levels; copy::Bool=false) braid(t, (p1, p2), levels; copy=copy) +#! format: on diff --git a/src/auxiliary/dicts.jl b/src/auxiliary/dicts.jl index e25de1ed..63164947 100644 --- a/src/auxiliary/dicts.jl +++ b/src/auxiliary/dicts.jl @@ -1,9 +1,9 @@ -struct SingletonDict{K, V} <: AbstractDict{K, V} +struct SingletonDict{K,V} <: AbstractDict{K,V} key::K value::V - SingletonDict{K, V}(p::Pair{K, V}) where {K, V} = new{K, V}(p.first, p.second) + SingletonDict{K,V}(p::Pair{K,V}) where {K,V} = new{K,V}(p.first, p.second) end -SingletonDict(p::Pair{K, V}) where {K, V} = SingletonDict{K, V}(p) +SingletonDict(p::Pair{K,V}) where {K,V} = SingletonDict{K,V}(p) function SingletonDict(g::Base.Generator) s = iterate(g) @assert s !== nothing @@ -19,14 +19,14 @@ Base.haskey(d::SingletonDict, key) = isequal(d.key, key) Base.getindex(d::SingletonDict, key) = isequal(d.key, key) ? d.value : throw(KeyError(key)) Base.get(d::SingletonDict, key, default) = isequal(d.key, key) ? d.value : default -Base.iterate(d::SingletonDict, s = true) = s ? ((d.key => d.value), false) : nothing +Base.iterate(d::SingletonDict, s=true) = s ? ((d.key => d.value), false) : nothing -struct VectorDict{K, V} <: AbstractDict{K, V} +struct VectorDict{K,V} <: AbstractDict{K,V} keys::Vector{K} values::Vector{V} end -VectorDict{K, V}() where {K, V} = VectorDict{K, V}(Vector{K}(), Vector{V}()) -function VectorDict{K, V}(kv) where {K, V} +VectorDict{K,V}() where {K,V} = VectorDict{K,V}(Vector{K}(), Vector{V}()) +function VectorDict{K,V}(kv) where {K,V} keys = Vector{K}() values = Vector{V}() if Base.IteratorSize(kv) !== SizeUnknown() @@ -37,24 +37,26 @@ function VectorDict{K, V}(kv) where {K, V} push!(keys, k) push!(values, v) end - return VectorDict{K, V}(keys, values) + return VectorDict{K,V}(keys, values) end -VectorDict(kv::Pair{K, V}...) where {K, V} = VectorDict{K, V}(kv) +VectorDict(kv::Pair{K,V}...) where {K,V} = VectorDict{K,V}(kv) VectorDict(g::Base.Generator) = VectorDict(g...) Base.length(d::VectorDict) = length(d.keys) -Base.sizehint!(d::VectorDict, newsz) = (sizehint!(d.keys, newsz); sizehint!(d.values, newsz); return d) +function Base.sizehint!(d::VectorDict, newsz) + (sizehint!(d.keys, newsz); sizehint!(d.values, newsz); return d) +end @propagate_inbounds getpair(d::VectorDict, i::Integer) = d.keys[i] => d.values[i] Base.copy(d::VectorDict) = VectorDict(copy(d.keys), copy(d.values)) -Base.empty(::VectorDict, ::Type{K}, ::Type{V}) where {K, V} = VectorDict{K, V}() +Base.empty(::VectorDict, ::Type{K}, ::Type{V}) where {K,V} = VectorDict{K,V}() Base.empty!(d::VectorDict) = (empty!(d.keys); empty!(d.values); return d) function Base.delete!(d::VectorDict, key) i = findfirst(isequal(key), d.keys) if !(i === nothing || i == 0) - deleteat!(d.keys , i) + deleteat!(d.keys, i) deleteat!(d.values, i) end return d @@ -87,54 +89,54 @@ function Base.get(d::VectorDict, key, default) end end -function Base.iterate(d::VectorDict, s = 1) +function Base.iterate(d::VectorDict, s=1) @inbounds if s > length(d) return nothing else - return (d.keys[s] => d.values[s]), s+1 + return (d.keys[s] => d.values[s]), s + 1 end end -struct SortedVectorDict{K, V} <: AbstractDict{K, V} +struct SortedVectorDict{K,V} <: AbstractDict{K,V} keys::Vector{K} values::Vector{V} - function SortedVectorDict{K, V}(pairs::Vector{Pair{K, V}}) where {K, V} - pairs = sort!(pairs, by=first) - return new{K, V}(first.(pairs), last.(pairs)) + function SortedVectorDict{K,V}(pairs::Vector{Pair{K,V}}) where {K,V} + pairs = sort!(pairs; by=first) + return new{K,V}(first.(pairs), last.(pairs)) end - function SortedVectorDict{K, V}(keys::Vector{K}, values::Vector{V}) where {K, V} + function SortedVectorDict{K,V}(keys::Vector{K}, values::Vector{V}) where {K,V} @assert issorted(keys) - new{K, V}(keys, values) + return new{K,V}(keys, values) end - SortedVectorDict{K, V}() where {K, V} = new{K, V}(Vector{K}(undef, 0), Vector{V}(undef, 0)) + SortedVectorDict{K,V}() where {K,V} = new{K,V}(Vector{K}(undef, 0), Vector{V}(undef, 0)) end -SortedVectorDict{K, V}(kv::Pair{K, V}...) where {K, V} = SortedVectorDict{K, V}(kv) -function SortedVectorDict{K, V}(kv) where {K, V} - d = SortedVectorDict{K, V}() +SortedVectorDict{K,V}(kv::Pair{K,V}...) where {K,V} = SortedVectorDict{K,V}(kv) +function SortedVectorDict{K,V}(kv) where {K,V} + d = SortedVectorDict{K,V}() if Base.IteratorSize(kv) !== SizeUnknown() sizehint!(d, length(kv)) end for (k, v) in kv - push!(d, k=>v) + push!(d, k => v) end return d end -SortedVectorDict(pairs::Vector{Pair{K, V}}) where {K, V} = SortedVectorDict{K, V}(pairs) +SortedVectorDict(pairs::Vector{Pair{K,V}}) where {K,V} = SortedVectorDict{K,V}(pairs) @noinline function _no_pair_error() msg = "SortedVectorDict(kv): kv needs to be an iterator of pairs" throw(ArgumentError(msg)) end function SortedVectorDict(pairs::Vector) - all(p->isa(p, Pair), pairs) || _no_pair_error() - pairs = sort!(pairs, by=first) + all(p -> isa(p, Pair), pairs) || _no_pair_error() + pairs = sort!(pairs; by=first) keys = map(first, pairs) values = map(last, pairs) - return SortedVectorDict{eltype(keys), eltype(values)}(keys, values) + return SortedVectorDict{eltype(keys),eltype(values)}(keys, values) end -SortedVectorDict(kv::Pair{K, V}...) where {K, V} = SortedVectorDict{K, V}(kv) +SortedVectorDict(kv::Pair{K,V}...) where {K,V} = SortedVectorDict{K,V}(kv) -_getKV(::Type{Pair{K, V}}) where {K, V} = (K, V) +_getKV(::Type{Pair{K,V}}) where {K,V} = (K, V) function SortedVectorDict(kv) if Base.IteratorEltype(kv) === Base.HasEltype() P = eltype(kv) @@ -145,20 +147,22 @@ function SortedVectorDict(kv) end if P <: Pair && Base.isconcretetype(P) K, V = _getKV(P) - return SortedVectorDict{K, V}(kv) + return SortedVectorDict{K,V}(kv) else return SortedVectorDict(collect(kv)) end end -SortedVectorDict() = SortedVectorDict{Any, Any}() +SortedVectorDict() = SortedVectorDict{Any,Any}() Base.length(d::SortedVectorDict) = length(d.keys) -Base.sizehint!(d::SortedVectorDict, newsz) = +function Base.sizehint!(d::SortedVectorDict, newsz) (sizehint!(d.keys, newsz); sizehint!(d.values, newsz); return d) +end -Base.copy(d::SortedVectorDict{K, V}) where {K, V} = - SortedVectorDict{K, V}(copy(d.keys), copy(d.values)) -Base.empty(::SortedVectorDict, ::Type{K}, ::Type{V}) where {K, V} = SortedVectorDict{K, V}() +function Base.copy(d::SortedVectorDict{K,V}) where {K,V} + return SortedVectorDict{K,V}(copy(d.keys), copy(d.values)) +end +Base.empty(::SortedVectorDict, ::Type{K}, ::Type{V}) where {K,V} = SortedVectorDict{K,V}() Base.empty!(d::SortedVectorDict) = (empty!(d.keys); empty!(d.values); return d) # _searchsortedfirst(v::Vector, k) = searchsortedfirst(v, k) @@ -230,7 +234,7 @@ function Base.get(d::SortedVectorDict{K}, k, default) where {K} return (i <= length(d) && isequal(d.keys[i], key)) ? d.values[i] : default end end -function Base.get(f::Union{Function, Type}, d::SortedVectorDict{K}, k) where {K} +function Base.get(f::Union{Function,Type}, d::SortedVectorDict{K}, k) where {K} key = convert(K, k) if !isequal(k, key) return f() @@ -240,10 +244,10 @@ function Base.get(f::Union{Function, Type}, d::SortedVectorDict{K}, k) where {K} return (i <= length(d) && isequal(d.keys[i], key)) ? d.values[i] : f() end end -function Base.iterate(d::SortedVectorDict, i = 1) +function Base.iterate(d::SortedVectorDict, i=1) @inbounds if i > length(d) return nothing else - return (d.keys[i] => d.values[i]), i+1 + return (d.keys[i] => d.values[i]), i + 1 end end diff --git a/src/auxiliary/linalg.jl b/src/auxiliary/linalg.jl index 5c025468..25e53ca1 100644 --- a/src/auxiliary/linalg.jl +++ b/src/auxiliary/linalg.jl @@ -1,15 +1,16 @@ # custom wrappers for BLAS and LAPACK routines, together with some custom definitions using LinearAlgebra: BlasFloat, Char, BlasInt, LAPACK, LAPACKException, - DimensionMismatch, SingularException, PosDefException, chkstride1, checksquare, - triu! + DimensionMismatch, SingularException, PosDefException, chkstride1, + checksquare, + triu! set_num_blas_threads(n::Integer) = LinearAlgebra.BLAS.set_num_threads(n) get_num_blas_threads(n::Integer) = LinearAlgebra.BLAS.get_num_threads(n) # TODO: define for CuMatrix if we support this function _one!(A::DenseMatrix) - Threads.@threads for j = 1:size(A, 2) - @simd for i = 1:size(A, 1) + Threads.@threads for j in 1:size(A, 2) + @simd for i in 1:size(A, 1) @inbounds A[i, j] = i == j end end @@ -54,19 +55,19 @@ Base.adjoint(::QL) = RQ() Base.adjoint(::RQpos) = QLpos() Base.adjoint(::RQ) = QL() -Base.adjoint(alg::Union{SVD, SDD, Polar}) = alg +Base.adjoint(alg::Union{SVD,SDD,Polar}) = alg -_safesign(s::Real) = ifelse(s= n nhalf = div(n, 2) #swap columns in A - @inbounds for j = 1:nhalf, i = 1:m - A[i,j], A[i,n+1-j] = A[i,n+1-j], A[i,j] + @inbounds for j in 1:nhalf, i in 1:m + A[i, j], A[i, n + 1 - j] = A[i, n + 1 - j], A[i, j] end - Q, R = _leftorth!(A, isa(alg, QL) ? QR() : QRpos() , atol) + Q, R = _leftorth!(A, isa(alg, QL) ? QR() : QRpos(), atol) #swap columns in Q - @inbounds for j = 1:nhalf, i = 1:m - Q[i,j], Q[i,n+1-j] = Q[i,n+1-j], Q[i,j] + @inbounds for j in 1:nhalf, i in 1:m + Q[i, j], Q[i, n + 1 - j] = Q[i, n + 1 - j], Q[i, j] end #swap rows and columns in R - @inbounds for j = 1:nhalf, i = 1:n - R[i,j], R[n+1-i,n+1-j] = R[n+1-i,n+1-j], R[i,j] + @inbounds for j in 1:nhalf, i in 1:n + R[i, j], R[n + 1 - i, n + 1 - j] = R[n + 1 - i, n + 1 - j], R[i, j] end if isodd(n) - j = nhalf+1 - @inbounds for i = 1:nhalf - R[i,j], R[n+1-i,j] = R[n+1-i,j], R[i,j] + j = nhalf + 1 + @inbounds for i in 1:nhalf + R[i, j], R[n + 1 - i, j] = R[n + 1 - i, j], R[i, j] end end return Q, R end -function _leftorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD, Polar}, atol::Real) +function _leftorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD,SDD,Polar}, atol::Real) U, S, V = alg isa SVD ? LAPACK.gesvd!('S', 'S', A) : LAPACK.gesdd!('S', A) - if isa(alg, Union{SVD, SDD}) - n = count(s-> s .> atol, S) + if isa(alg, Union{SVD,SDD}) + n = count(s -> s .> atol, S) if n != length(S) - return U[:,1:n], lmul!(Diagonal(S[1:n]), V[1:n, :]) + return U[:, 1:n], lmul!(Diagonal(S[1:n]), V[1:n, :]) else return U, lmul!(Diagonal(S), V) end @@ -133,34 +134,34 @@ function _leftorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD, Polar}, Q = mul!(A, U, V) Sq = map!(sqrt, S, S) SqV = lmul!(Diagonal(Sq), V) - R = SqV'*SqV + R = SqV' * SqV return Q, R end end -function _leftnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{QR, QRpos}, atol::Real) +function _leftnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{QR,QRpos}, atol::Real) iszero(atol) || throw(ArgumentError("nonzero atol not supported by $alg")) m, n = size(A) m >= n || throw(ArgumentError("no null space if less rows than columns")) A, T = LAPACK.geqrt!(A, min(minimum(size(A)), 36)) - N = similar(A, m, max(0, m-n)); + N = similar(A, m, max(0, m - n)) fill!(N, 0) - for k = 1:m-n - N[n+k,k] = 1 + for k in 1:(m - n) + N[n + k, k] = 1 end - N = LAPACK.gemqrt!('L', 'N', A, T, N) + return N = LAPACK.gemqrt!('L', 'N', A, T, N) end -function _leftnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD}, atol::Real) +function _leftnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD,SDD}, atol::Real) size(A, 2) == 0 && return _one!(similar(A, (size(A, 1), size(A, 1)))) U, S, V = alg isa SVD ? LAPACK.gesvd!('A', 'N', A) : LAPACK.gesdd!('A', A) indstart = count(>(atol), S) + 1 return U[:, indstart:end] end -function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ, LQpos, RQ, RQpos}, - atol::Real) +function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ,LQpos,RQ,RQpos}, + atol::Real) iszero(atol) || throw(ArgumentError("nonzero atol not supported by $alg")) # TODO: geqrfp seems a bit slower than geqrt in the intermediate region around # matrix size 100, which is the interesting region. => Investigate and fix @@ -173,21 +174,21 @@ function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ, LQpos, RQ, RQ mhalf = div(m, 2) # swap columns in At - @inbounds for j = 1:mhalf, i = 1:n - At[i,j], At[i,m+1-j] = At[i,m+1-j], At[i,j] + @inbounds for j in 1:mhalf, i in 1:n + At[i, j], At[i, m + 1 - j] = At[i, m + 1 - j], At[i, j] end Qt, Rt = _leftorth!(At, isa(alg, RQ) ? QR() : QRpos(), atol) - @inbounds for j = 1:mhalf, i = 1:n - Qt[i,j], Qt[i,m+1-j] = Qt[i,m+1-j], Qt[i,j] + @inbounds for j in 1:mhalf, i in 1:n + Qt[i, j], Qt[i, m + 1 - j] = Qt[i, m + 1 - j], Qt[i, j] end - @inbounds for j = 1:mhalf, i = 1:m - Rt[i,j], Rt[m+1-i,m+1-j] = Rt[m+1-i,m+1-j], Rt[i,j] + @inbounds for j in 1:mhalf, i in 1:m + Rt[i, j], Rt[m + 1 - i, m + 1 - j] = Rt[m + 1 - i, m + 1 - j], Rt[i, j] end if isodd(m) - j = mhalf+1 - @inbounds for i = 1:mhalf - Rt[i,j], Rt[m+1-i,j] = Rt[m+1-i,j], Rt[i,j] + j = mhalf + 1 + @inbounds for i in 1:mhalf + Rt[i, j], Rt[m + 1 - i, j] = Rt[m + 1 - i, j], Rt[i, j] end end Q = transpose!(A, Qt) @@ -206,12 +207,12 @@ function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ, LQpos, RQ, RQ end end -function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD, Polar}, atol::Real) +function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD,SDD,Polar}, atol::Real) U, S, V = alg isa SVD ? LAPACK.gesvd!('S', 'S', A) : LAPACK.gesdd!('S', A) - if isa(alg, Union{SVD, SDD}) - n = count(s-> s .> atol, S) + if isa(alg, Union{SVD,SDD}) + n = count(s -> s .> atol, S) if n != length(S) - return rmul!(U[:,1:n], Diagonal(S[1:n])), V[1:n,:] + return rmul!(U[:, 1:n], Diagonal(S[1:n])), V[1:n, :] else return rmul!(U, Diagonal(S)), V end @@ -220,33 +221,33 @@ function _rightorth!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD, Polar}, Q = mul!(A, U, V) Sq = map!(sqrt, S, S) USq = rmul!(U, Diagonal(Sq)) - L = USq*USq' + L = USq * USq' return L, Q end end -function _rightnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ, LQpos}, atol::Real) +function _rightnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{LQ,LQpos}, atol::Real) iszero(atol) || throw(ArgumentError("nonzero atol not supported by $alg")) m, n = size(A) k = min(m, n) At = adjoint!(similar(A, n, m), A) At, T = LAPACK.geqrt!(At, min(k, 36)) - N = similar(A, max(n-m, 0), n); + N = similar(A, max(n - m, 0), n) fill!(N, 0) - for k = 1:n-m - N[k,m+k] = 1 + for k in 1:(n - m) + N[k, m + k] = 1 end - N = LAPACK.gemqrt!('R', eltype(At) <: Real ? 'T' : 'C', At, T, N) + return N = LAPACK.gemqrt!('R', eltype(At) <: Real ? 'T' : 'C', At, T, N) end -function _rightnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD}, atol::Real) +function _rightnull!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD,SDD}, atol::Real) size(A, 1) == 0 && return _one!(similar(A, (size(A, 2), size(A, 2)))) U, S, V = alg isa SVD ? LAPACK.gesvd!('N', 'A', A) : LAPACK.gesdd!('A', A) indstart = count(>(atol), S) + 1 return V[indstart:end, :] end -function _svd!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD, SDD}) +function _svd!(A::StridedMatrix{<:BlasFloat}, alg::Union{SVD,SDD}) U, S, V = alg isa SVD ? LAPACK.gesvd!('S', 'S', A) : LAPACK.gesdd!('S', A) return U, S, V end diff --git a/src/auxiliary/random.jl b/src/auxiliary/random.jl index 04536d12..d40c16f6 100644 --- a/src/auxiliary/random.jl +++ b/src/auxiliary/random.jl @@ -5,8 +5,9 @@ randnormal(dims::Base.Dims) = randnormal(Float64, dims) randnormal(::Type{T}, dims::Base.Dims) where {T<:Number} = randn(T, dims) randisometry(dims::Base.Dims{2}) = randisometry(Float64, dims) -randisometry(::Type{T}, dims::Base.Dims{2}) where {T<:Number} = - dims[1] >= dims[2] ? _leftorth!(randnormal(T, dims), QRpos(), 0)[1] : - throw(DimensionMismatch("cannot create isometric matrix with dimensions $dims; isometry needs to be tall or square")) +function randisometry(::Type{T}, dims::Base.Dims{2}) where {T<:Number} + return dims[1] >= dims[2] ? _leftorth!(randnormal(T, dims), QRpos(), 0)[1] : + throw(DimensionMismatch("cannot create isometric matrix with dimensions $dims; isometry needs to be tall or square")) +end const randhaar = randisometry diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index 10ed9d27..40fbac3b 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -12,18 +12,18 @@ has `M=max(0, N-2)` inner lines. Furthermore, for `FusionStyle(I) isa GenericFus the `L=max(0, N-1)` corresponding vertices carry a label of type `T`. If `FusionStyle(I) isa MultiplicityFreeFusion, `T = Nothing`. """ -struct FusionTree{I<:Sector, N, M, L, T} - uncoupled::NTuple{N, I} +struct FusionTree{I<:Sector,N,M,L,T} + uncoupled::NTuple{N,I} coupled::I - isdual::NTuple{N, Bool} - innerlines::NTuple{M, I} # M = N-2 - vertices::NTuple{L, T} # L = N-1 - function FusionTree{I, N, M, L, T}(uncoupled::NTuple{N, I}, - coupled::I, - isdual::NTuple{N, Bool}, - innerlines::NTuple{M, I}, - vertices::NTuple{L, T}) where - {I<:Sector, N, M, L, T} + isdual::NTuple{N,Bool} + innerlines::NTuple{M,I} # M = N-2 + vertices::NTuple{L,T} # L = N-1 + function FusionTree{I,N,M,L,T}(uncoupled::NTuple{N,I}, + coupled::I, + isdual::NTuple{N,Bool}, + innerlines::NTuple{M,I}, + vertices::NTuple{L,T}) where + {I<:Sector,N,M,L,T} # if N == 0 # @assert coupled == one(coupled) # elseif N == 1 @@ -37,38 +37,34 @@ struct FusionTree{I<:Sector, N, M, L, T} # end # @assert coupled ∈ ⊗(innerlines[N-2], uncoupled[N]) # end - new{I, N, M, L, T}(uncoupled, coupled, isdual, innerlines, vertices) + return new{I,N,M,L,T}(uncoupled, coupled, isdual, innerlines, vertices) end end -function FusionTree{I}(uncoupled::NTuple{N, Any}, - coupled, - isdual::NTuple{N, Bool}, - innerlines, - vertices = ntuple(n->nothing, max(0, N-1)) - ) where {I<:Sector, N} +function FusionTree{I}(uncoupled::NTuple{N,Any}, coupled, + isdual::NTuple{N,Bool}, innerlines, + vertices=ntuple(n -> nothing, max(0, N - 1))) where {I<:Sector,N} if FusionStyle(I) isa GenericFusion - fusiontreetype(I, N)(map(s->convert(I, s), uncoupled), - convert(I, coupled), isdual, map(s->convert(I, s), innerlines), vertices) + fusiontreetype(I, N)(map(s -> convert(I, s), uncoupled), + convert(I, coupled), isdual, + map(s -> convert(I, s), innerlines), vertices) else - vertices′ = ntuple(n->nothing, max(0, N-1)) + vertices′ = ntuple(n -> nothing, max(0, N - 1)) if vertices == vertices′ || all(isone, vertices) - fusiontreetype(I, N)(map(s->convert(I, s), uncoupled), - convert(I, coupled), isdual, map(s->convert(I, s), innerlines), vertices) + fusiontreetype(I, N)(map(s -> convert(I, s), uncoupled), + convert(I, coupled), isdual, + map(s -> convert(I, s), innerlines), vertices) else throw(ArgumentError("Incorrect fusion vertices")) end end end -function FusionTree(uncoupled::NTuple{N, I}, - coupled::I, - isdual::NTuple{N, Bool}, - innerlines, - vertices = ntuple(n->nothing, max(0, N-1)) - ) where {I<:Sector, N} +function FusionTree(uncoupled::NTuple{N,I}, coupled::I, + isdual::NTuple{N,Bool}, innerlines, + vertices=ntuple(n -> nothing, max(0, N - 1))) where {I<:Sector,N} if FusionStyle(I) isa GenericFusion fusiontreetype(I, N)(uncoupled, coupled, isdual, innerlines, vertices) else - vertices′ = ntuple(n->nothing, max(0, N-1)) + vertices′ = ntuple(n -> nothing, max(0, N - 1)) if vertices == vertices′ || all(isone, vertices) fusiontreetype(I, N)(uncoupled, coupled, isdual, innerlines, vertices′) else @@ -77,25 +73,27 @@ function FusionTree(uncoupled::NTuple{N, I}, end end -function FusionTree{I}(uncoupled::NTuple{N}, coupled = one(I), - isdual = ntuple(n->false, N)) where {I<:Sector, N} +function FusionTree{I}(uncoupled::NTuple{N}, coupled=one(I), + isdual=ntuple(n -> false, N)) where {I<:Sector,N} FusionStyle(I) isa UniqueFusion || error("fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`") - FusionTree{I}(map(s->convert(I, s), uncoupled), convert(I, coupled), isdual, - _abelianinner(map(s->convert(I, s), (uncoupled..., dual(coupled))))) + return FusionTree{I}(map(s -> convert(I, s), uncoupled), convert(I, coupled), isdual, + _abelianinner(map(s -> convert(I, s), + (uncoupled..., dual(coupled))))) end -function FusionTree(uncoupled::NTuple{N, I}, coupled::I = one(I), - isdual = ntuple(n->false, N)) where {I<:Sector, N} +function FusionTree(uncoupled::NTuple{N,I}, coupled::I=one(I), + isdual=ntuple(n -> false, N)) where {I<:Sector,N} FusionStyle(I) isa UniqueFusion || error("fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`") - FusionTree{I}(uncoupled, coupled, isdual, _abelianinner((uncoupled..., dual(coupled)))) + return FusionTree{I}(uncoupled, coupled, isdual, + _abelianinner((uncoupled..., dual(coupled)))) end # Properties sectortype(::Type{<:FusionTree{I}}) where {I<:Sector} = I FusionStyle(::Type{<:FusionTree{I}}) where {I<:Sector} = FusionStyle(I) BraidingStyle(::Type{<:FusionTree{I}}) where {I<:Sector} = BraidingStyle(I) -Base.length(::Type{<:FusionTree{<:Sector, N}}) where {N} = N +Base.length(::Type{<:FusionTree{<:Sector,N}}) where {N} = N sectortype(f::FusionTree) = sectortype(typeof(f)) FusionStyle(f::FusionTree) = FusionStyle(typeof(f)) @@ -113,19 +111,19 @@ function Base.hash(f::FusionTree{I}, h::UInt) where {I} end return h end -function Base.isequal(f₁::FusionTree{I, N}, f₂::FusionTree{I, N}) where {I<:Sector, N} +function Base.isequal(f₁::FusionTree{I,N}, f₂::FusionTree{I,N}) where {I<:Sector,N} f₁.coupled == f₂.coupled || return false - @inbounds for i = 1:N + @inbounds for i in 1:N f₁.uncoupled[i] == f₂.uncoupled[i] || return false f₁.isdual[i] == f₂.isdual[i] || return false end if FusionStyle(I) isa MultipleFusion - @inbounds for i=1:N-2 + @inbounds for i in 1:(N - 2) f₁.innerlines[i] == f₂.innerlines[i] || return false end end if FusionStyle(I) isa GenericFusion - @inbounds for i=1:N-1 + @inbounds for i in 1:(N - 1) f₁.vertices[i] == f₂.vertices[i] || return false end end @@ -133,24 +131,23 @@ function Base.isequal(f₁::FusionTree{I, N}, f₂::FusionTree{I, N}) where {I<: end Base.isequal(f₁::FusionTree, f₂::FusionTree) = false - # Facilitate getting correct fusion tree types function fusiontreetype(::Type{I}, N::Int) where {I<:Sector} if N === 0 - FusionTree{I, 0, 0, 0, vertex_labeltype(I)} + FusionTree{I,0,0,0,vertex_labeltype(I)} elseif N === 1 - FusionTree{I, 1, 0, 0, vertex_labeltype(I)} + FusionTree{I,1,0,0,vertex_labeltype(I)} else - FusionTree{I, N, N-2, N-1, vertex_labeltype(I)} + FusionTree{I,N,N - 2,N - 1,vertex_labeltype(I)} end end # converting to actual array -function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 0}) where {I} +function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,0}) where {I} X = convert(A, fusiontensor(one(I), one(I), one(I)))[1, 1, :] return X end -function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 1}) where {I} +function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,1}) where {I} c = f.coupled if f.isdual[1] sqrtdc = sqrtdim(c) @@ -162,7 +159,7 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 1}) where {I} return X end -function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 2}) where {I} +function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,2}) where {I} a, b = f.uncoupled isduala, isdualb = f.isdual c = f.coupled @@ -174,13 +171,15 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I, 2}) where {I} Xtemp = X X = similar(Xtemp) Za = convert(A, FusionTree((a,), a, (isduala,), ())) - TO.tensorcontract!(X, ((1,2,3), ()), Za, ((1,), (2,)), :N, Xtemp, ((1,), (2,3)), :N, true, false) + TO.tensorcontract!(X, ((1, 2, 3), ()), Za, ((1,), (2,)), :N, Xtemp, ((1,), (2, 3)), + :N, true, false) end if isdualb Xtemp = X X = similar(Xtemp) Zb = convert(A, FusionTree((b,), b, (isdualb,), ())) - TO.tensorcontract!(X, ((2, 1, 3), ()), Zb, ((1,), (2,)), :N, Xtemp, ((2,), (1, 3)), :N, true, false) + TO.tensorcontract!(X, ((2, 1, 3), ()), Zb, ((1,), (2,)), :N, Xtemp, ((2,), (1, 3)), + :N, true, false) end return X end @@ -189,7 +188,7 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,N}) where {I,N} tailout = (f.innerlines[1], TupleTools.tail2(f.uncoupled)...) isdualout = (false, TupleTools.tail2(f.isdual)...) ftail = FusionTree(tailout, f.coupled, isdualout, - Base.tail(f.innerlines), Base.tail(f.vertices)) + Base.tail(f.innerlines), Base.tail(f.vertices)) Ctail = convert(A, ftail) f₁ = FusionTree((f.uncoupled[1], f.uncoupled[2]), f.innerlines[1], (f.isdual[1], f.isdual[2]), (), (f.vertices[1],)) @@ -198,18 +197,19 @@ function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,N}) where {I,N} d1 = size(C1) X = similar(C1, (d1[1], d1[2], Base.tail(dtail)...)) trivialtuple = ntuple(identity, Val(N)) - return TO.tensorcontract!(X, ((trivialtuple..., N+1), ()), C1, ((1,2), (3,)), :N, Ctail, ((1,), Base.tail(trivialtuple)), :N, true, false) + return TO.tensorcontract!(X, ((trivialtuple..., N + 1), ()), C1, ((1, 2), (3,)), :N, + Ctail, ((1,), Base.tail(trivialtuple)), :N, true, false) end # Show methods -function Base.show(io::IO, t::FusionTree{I, N, M, K, Nothing}) where {I<:Sector, N, M, K} - print(IOContext(io, :typeinfo => I), "FusionTree{", type_repr(I), "}(", - t.uncoupled, ", ", t.coupled, ", ", t.isdual, ", ", t.innerlines, ")") +function Base.show(io::IO, t::FusionTree{I,N,M,K,Nothing}) where {I<:Sector,N,M,K} + return print(IOContext(io, :typeinfo => I), "FusionTree{", type_repr(I), "}(", + t.uncoupled, ", ", t.coupled, ", ", t.isdual, ", ", t.innerlines, ")") end function Base.show(io::IO, t::FusionTree{I}) where {I<:Sector} - print(IOContext(io, :typeinfo => I), "FusionTree{", type_repr(I), "}(", - t.uncoupled, ", ", t.coupled, ", ", t.isdual, ",", - t.innerlines, ", ", t.vertices, ")") + return print(IOContext(io, :typeinfo => I), "FusionTree{", type_repr(I), "}(", + t.uncoupled, ", ", t.coupled, ", ", t.isdual, ",", + t.innerlines, ", ", t.vertices, ")") end # Manipulate fusion trees @@ -221,13 +221,16 @@ include("iterator.jl") # auxiliary routines # _abelianinner: generate the inner indices for given outer indices in the abelian case _abelianinner(outer::Tuple{}) = () -_abelianinner(outer::Tuple{I}) where {I<:Sector} = - outer[1] == one(I) ? () : throw(SectorMismatch()) -_abelianinner(outer::Tuple{I, I}) where {I<:Sector} = - outer[1] == dual(outer[2]) ? () : throw(SectorMismatch()) -_abelianinner(outer::Tuple{I, I, I}) where {I<:Sector} = - first(⊗(outer...)) == one(I) ? () : throw(SectorMismatch()) -function _abelianinner(outer::NTuple{N, I}) where {I<:Sector, N} +function _abelianinner(outer::Tuple{I}) where {I<:Sector} + return outer[1] == one(I) ? () : throw(SectorMismatch()) +end +function _abelianinner(outer::Tuple{I,I}) where {I<:Sector} + return outer[1] == dual(outer[2]) ? () : throw(SectorMismatch()) +end +function _abelianinner(outer::Tuple{I,I,I}) where {I<:Sector} + return first(⊗(outer...)) == one(I) ? () : throw(SectorMismatch()) +end +function _abelianinner(outer::NTuple{N,I}) where {I<:Sector,N} c = first(outer[1] ⊗ outer[2]) return (c, _abelianinner((c, TupleTools.tail2(outer)...))...) end diff --git a/src/fusiontrees/iterator.jl b/src/fusiontrees/iterator.jl index 186831ed..c724588f 100644 --- a/src/fusiontrees/iterator.jl +++ b/src/fusiontrees/iterator.jl @@ -1,57 +1,56 @@ # FusionTreeIterator: # iterate over fusion trees for fixed coupled and uncoupled sector labels #==============================================================================# -function fusiontrees(uncoupled::NTuple{N, I}, coupled::I = one(I), - isdual::NTuple{N, Bool} = ntuple(n->false, Val(N))) where - {N, I<:Sector} - FusionTreeIterator{I, N}(uncoupled, coupled, isdual) +function fusiontrees(uncoupled::NTuple{N,I}, coupled::I=one(I), + isdual::NTuple{N,Bool}=ntuple(n -> false, Val(N))) where + {N,I<:Sector} + return FusionTreeIterator{I,N}(uncoupled, coupled, isdual) end -struct FusionTreeIterator{I<:Sector, N} - uncoupled::NTuple{N, I} +struct FusionTreeIterator{I<:Sector,N} + uncoupled::NTuple{N,I} coupled::I - isdual::NTuple{N, Bool} + isdual::NTuple{N,Bool} end Base.IteratorSize(::FusionTreeIterator) = Base.HasLength() Base.IteratorEltype(::FusionTreeIterator) = Base.HasEltype() -Base.eltype(T::Type{FusionTreeIterator{I, N}}) where {I<:Sector, N} = - fusiontreetype(I, N) +Base.eltype(T::Type{FusionTreeIterator{I,N}}) where {I<:Sector,N} = fusiontreetype(I, N) Base.length(iter::FusionTreeIterator) = _fusiondim(iter.uncoupled, iter.coupled) _fusiondim(u::Tuple{}, c::I) where {I<:Sector} = Int(one(c) == c) _fusiondim(u::Tuple{I}, c::I) where {I<:Sector} = Int(u[1] == c) -_fusiondim((a, b)::Tuple{I, I}, c::I) where {I<:Sector} = Int(Nsymbol(a, b, c)) -function _fusiondim(u::Tuple{I, I, Vararg{I}}, c::I) where {I<:Sector} +_fusiondim((a, b)::Tuple{I,I}, c::I) where {I<:Sector} = Int(Nsymbol(a, b, c)) +function _fusiondim(u::Tuple{I,I,Vararg{I}}, c::I) where {I<:Sector} a = u[1] b = u[2] d = 0 for c′ in a ⊗ b - d += Nsymbol(a, b, c′)*_fusiondim((c′, TupleTools.tail2(u)...), c) + d += Nsymbol(a, b, c′) * _fusiondim((c′, TupleTools.tail2(u)...), c) end return d end # * Iterator methods: # Start with special cases: -function Base.iterate(it::FusionTreeIterator{I, 0}, - state = (it.coupled != one(I))) where {I<:Sector} +function Base.iterate(it::FusionTreeIterator{I,0}, + state=(it.coupled != one(I))) where {I<:Sector} state && return nothing T = vertex_labeltype(I) - tree = FusionTree{I, 0, 0, 0, T}((), one(I), (), (), ()) + tree = FusionTree{I,0,0,0,T}((), one(I), (), (), ()) return tree, true end -function Base.iterate(it::FusionTreeIterator{I, 1}, - state = (it.uncoupled[1] != it.coupled)) where {I<:Sector} +function Base.iterate(it::FusionTreeIterator{I,1}, + state=(it.uncoupled[1] != it.coupled)) where {I<:Sector} state && return nothing T = vertex_labeltype(I) - tree = FusionTree{I, 1, 0, 0, T}(it.uncoupled, it.coupled, it.isdual, (), ()) + tree = FusionTree{I,1,0,0,T}(it.uncoupled, it.coupled, it.isdual, (), ()) return tree, true end # General case: -function Base.iterate(it::FusionTreeIterator{I, N} where {N}) where {I<:Sector} +function Base.iterate(it::FusionTreeIterator{I,N} where {N}) where {I<:Sector} next = _iterate(it.uncoupled, it.coupled) next === nothing && return nothing lines, vertices, states = next @@ -59,7 +58,7 @@ function Base.iterate(it::FusionTreeIterator{I, N} where {N}) where {I<:Sector} f = FusionTree(it.uncoupled, it.coupled, it.isdual, lines, vertexlabels) return f, (lines, vertices, states) end -function Base.iterate(it::FusionTreeIterator{I, N} where {N}, state) where {I<:Sector} +function Base.iterate(it::FusionTreeIterator{I,N} where {N}, state) where {I<:Sector} next = _iterate(it.uncoupled, it.coupled, state...) next === nothing && return nothing lines, vertices, states = next @@ -68,12 +67,13 @@ function Base.iterate(it::FusionTreeIterator{I, N} where {N}, state) where {I<:S return f, (lines, vertices, states) end -labelvertices(uncoupled::NTuple{2, I}, coupled::I, lines::Tuple{}, - vertices::Tuple{Int}) where {I<:Sector} = - (vertex_ind2label(vertices[1], uncoupled..., coupled),) +function labelvertices(uncoupled::NTuple{2,I}, coupled::I, lines::Tuple{}, + vertices::Tuple{Int}) where {I<:Sector} + return (vertex_ind2label(vertices[1], uncoupled..., coupled),) +end -function labelvertices(uncoupled::NTuple{N, I}, coupled::I, lines, - vertices) where {I<:Sector, N} +function labelvertices(uncoupled::NTuple{N,I}, coupled::I, lines, + vertices) where {I<:Sector,N} c = lines[1] resttree = tuple(c, TupleTools.tail2(uncoupled)...) rest = labelvertices(resttree, coupled, tail(lines), tail(vertices)) @@ -82,15 +82,15 @@ function labelvertices(uncoupled::NTuple{N, I}, coupled::I, lines, end # Actual implementation -@inline function _iterate(uncoupled::NTuple{2, I}, coupled::I, lines = (), - vertices = (0,), states = ()) where {I<:Sector} +@inline function _iterate(uncoupled::NTuple{2,I}, coupled::I, lines=(), + vertices=(0,), states=()) where {I<:Sector} a, b = uncoupled n = vertices[1] + 1 n > Nsymbol(a, b, coupled) && return nothing return (), (n,), () end -function _iterate(uncoupled::NTuple{N, I}, coupled::I) where {N, I<:Sector} +function _iterate(uncoupled::NTuple{N,I}, coupled::I) where {N,I<:Sector} a, b, = uncoupled it = a ⊗ b next = iterate(it) @@ -114,8 +114,8 @@ function _iterate(uncoupled::NTuple{N, I}, coupled::I) where {N, I<:Sector} return lines, vertices, states end -function _iterate(uncoupled::NTuple{N, I}, coupled::I, lines, vertices, - states) where {N, I<:Sector} +function _iterate(uncoupled::NTuple{N,I}, coupled::I, lines, vertices, + states) where {N,I<:Sector} a, b, = uncoupled it = a ⊗ b c = lines[1] diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index 36b2ee19..a2acbab8 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -13,11 +13,11 @@ Attach a fusion tree `f₂` to the uncoupled leg `i` of the fusion tree `f₁` a into a linear combination of fusion trees in standard form. This requires that `f₂.coupled == f₁.uncoupled[i]` and `f₁.isdual[i] == false`. """ -function insertat(f₁::FusionTree{I}, i::Int, f₂::FusionTree{I, 0}) where {I} +function insertat(f₁::FusionTree{I}, i::Int, f₂::FusionTree{I,0}) where {I} # this actually removes uncoupled line i, which should be trivial (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) - coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] + coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1] uncoupled = TupleTools.deleteat(f₁.uncoupled, i) coupled = f₁.coupled @@ -25,26 +25,26 @@ function insertat(f₁::FusionTree{I}, i::Int, f₂::FusionTree{I, 0}) where {I} if length(uncoupled) <= 2 inner = () else - inner = TupleTools.deleteat(f₁.innerlines, max(1, i-2)) + inner = TupleTools.deleteat(f₁.innerlines, max(1, i - 2)) end if length(uncoupled) <= 1 vertices = () else - vertices = TupleTools.deleteat(f₁.vertices, max(1, i-1)) + vertices = TupleTools.deleteat(f₁.vertices, max(1, i - 1)) end f = FusionTree(uncoupled, coupled, isdual, inner, vertices) return fusiontreedict(I)(f => coeff) end -function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 1}) where {I} +function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I,1}) where {I} # identity operation (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) - coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] + coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1] isdual′ = TupleTools.setindex(f₁.isdual, f₂.isdual[1], i) f = FusionTree{I}(f₁.uncoupled, f₁.coupled, isdual′, f₁.innerlines, f₁.vertices) return fusiontreedict(I)(f => coeff) end -function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 2}) where {I} +function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I,2}) where {I} # elementary building block, (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) @@ -59,14 +59,14 @@ function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 2}) where {I} isdual′ = (isdualb, isdualc, tail(isdual)...) inner′ = (uncoupled[1], inner...) vertices′ = (f₂.vertices..., f₁.vertices...) - coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] + coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1] f′ = FusionTree(uncoupled′, coupled, isdual′, inner′, vertices′) return fusiontreedict(I)(f′ => coeff) end uncoupled′ = TupleTools.insertafter(TupleTools.setindex(uncoupled, b, i), i, (c,)) isdual′ = TupleTools.insertafter(TupleTools.setindex(isdual, isdualb, i), i, (isdualc,)) inner_extended = (uncoupled[1], inner..., coupled) - a = inner_extended[i-1] + a = inner_extended[i - 1] d = inner_extended[i] e′ = uncoupled[i] if FusionStyle(I) isa MultiplicityFreeFusion @@ -74,10 +74,10 @@ function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 2}) where {I} for e in a ⊗ b coeff = conj(Fsymbol(a, b, c, d, e, e′)) iszero(coeff) && continue - inner′ = TupleTools.insertafter(inner, i-2, (e,)) + inner′ = TupleTools.insertafter(inner, i - 2, (e,)) f′ = FusionTree(uncoupled′, coupled, isdual′, inner′) if @isdefined newtrees - push!(newtrees, f′=> coeff) + push!(newtrees, f′ => coeff) else newtrees = fusiontreedict(I)(f′ => coeff) end @@ -86,15 +86,15 @@ function insertat(f₁::FusionTree{I}, i, f₂::FusionTree{I, 2}) where {I} else local newtrees κ = f₂.vertices[1] - λ = f₁.vertices[i-1] + λ = f₁.vertices[i - 1] for e in a ⊗ b - inner′ = TupleTools.insertafter(inner, i-2, (e,)) + inner′ = TupleTools.insertafter(inner, i - 2, (e,)) Fmat = Fsymbol(a, b, c, d, e, e′) - for μ = 1:size(Fmat, 1), ν = 1:size(Fmat, 2) - coeff = conj(Fmat[μ,ν,κ,λ]) + for μ in 1:size(Fmat, 1), ν in 1:size(Fmat, 2) + coeff = conj(Fmat[μ, ν, κ, λ]) iszero(coeff) && continue - vertices′ = TupleTools.setindex(f₁.vertices, ν, i-1) - vertices′ = TupleTools.insertafter(vertices′, i-2, (μ,)) + vertices′ = TupleTools.setindex(f₁.vertices, ν, i - 1) + vertices′ = TupleTools.insertafter(vertices′, i - 2, (μ,)) f′ = FusionTree(uncoupled′, coupled, isdual′, inner′, vertices′) if @isdefined newtrees push!(newtrees, f′ => coeff) @@ -110,7 +110,7 @@ function insertat(f₁::FusionTree{I,N₁}, i, f₂::FusionTree{I,N₂}) where { F = fusiontreetype(I, N₁ + N₂ - 1) (f₁.uncoupled[i] == f₂.coupled && !f₁.isdual[i]) || throw(SectorMismatch("cannot connect $(f₂.uncoupled) to $(f₁.uncoupled[i])")) - coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1] + coeff = Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1] T = typeof(coeff) if length(f₁) == 1 return fusiontreedict(I){F,T}(f₂ => coeff) @@ -130,10 +130,10 @@ function insertat(f₁::FusionTree{I,N₁}, i, f₂::FusionTree{I,N₂}) where { for (f, coeff) in insertat(f₁, i, f₂′′) for (f′, coeff′) in insertat(f, i, f₂′) if @isdefined newtrees - coeff′′ = coeff*coeff′ + coeff′′ = coeff * coeff′ newtrees[f′] = get(newtrees, f′, zero(coeff′′)) + coeff′′ else - newtrees = fusiontreedict(I){F,T}(f′ => coeff*coeff′) + newtrees = fusiontreedict(I){F,T}(f′ => coeff * coeff′) end end end @@ -153,7 +153,7 @@ remaining `N-M` uncoupled sectors of `f`. It couples to the same sector as `f`. operation is the inverse of `insertat` in the sense that if `f₁, f₂ = split(t, M) ⇒ f == insertat(f₂, 1, f₁)`. """ -@inline function split(f::FusionTree{I, N}, M::Int) where {I, N} +@inline function split(f::FusionTree{I,N}, M::Int) where {I,N} if M > N || M < 0 throw(ArgumentError("M should be between 0 and N = $N")) elseif M === N @@ -177,21 +177,21 @@ operation is the inverse of `insertat` in the sense that if return f₁, FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2) end else - uncoupled1 = ntuple(n->f.uncoupled[n], M) - isdual1 = ntuple(n->f.isdual[n], M) - innerlines1 = ntuple(n->f.innerlines[n], max(0, M-2)) - coupled1 = f.innerlines[M-1] - vertices1 = ntuple(n->f.vertices[n], M-1) + uncoupled1 = ntuple(n -> f.uncoupled[n], M) + isdual1 = ntuple(n -> f.isdual[n], M) + innerlines1 = ntuple(n -> f.innerlines[n], max(0, M - 2)) + coupled1 = f.innerlines[M - 1] + vertices1 = ntuple(n -> f.vertices[n], M - 1) uncoupled2 = ntuple(N - M + 1) do n - n == 1 ? f.innerlines[M - 1] : f.uncoupled[M + n - 1] + return n == 1 ? f.innerlines[M - 1] : f.uncoupled[M + n - 1] end isdual2 = ntuple(N - M + 1) do n - n == 1 ? false : f.isdual[M + n - 1] + return n == 1 ? false : f.isdual[M + n - 1] end - innerlines2 = ntuple(n->f.innerlines[M-1+n], N-M-1) + innerlines2 = ntuple(n -> f.innerlines[M - 1 + n], N - M - 1) coupled2 = f.coupled - vertices2 = ntuple(n->f.vertices[M-1+n], N-M) + vertices2 = ntuple(n -> f.vertices[M - 1 + n], N - M) f₁ = FusionTree{I}(uncoupled1, coupled1, isdual1, innerlines1, vertices1) f₂ = FusionTree{I}(uncoupled2, coupled2, isdual2, innerlines2, vertices2) @@ -209,8 +209,8 @@ sectors are those of `f₁` followed by those of `f₂`, and where the two coupl `FusionStyle(I) == GenericFusion()`, also a degeneracy label `μ` for the fusion of the coupled sectors of `f₁` and `f₂` to `c` needs to be specified. """ -function merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, - c::I, μ = nothing) where {I, N₁, N₂} +function merge(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}, + c::I, μ=nothing) where {I,N₁,N₂} if FusionStyle(I) isa GenericFusion && μ === nothing throw(ArgumentError("vertex label for merging required")) end @@ -220,12 +220,12 @@ function merge(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}, f₀ = FusionTree((f₁.coupled, f₂.coupled), c, (false, false), (), (μ,)) f, coeff = first(insertat(f₀, 1, f₁)) # takes fast path, single output @assert coeff == one(coeff) - return insertat(f, N₁+1, f₂) + return insertat(f, N₁ + 1, f₂) end -function merge(f₁::FusionTree{I, 0}, f₂::FusionTree{I, 0}, c::I, μ = nothing) where {I} +function merge(f₁::FusionTree{I,0}, f₂::FusionTree{I,0}, c::I, μ=nothing) where {I} c == one(I) || throw(SectorMismatch("cannot fuse sectors $(f₁.coupled) and $(f₂.coupled) to $c")) - return fusiontreedict(I)(f₁=>Fsymbol(c, c, c, c, c, c)[1,1,1,1]) + return fusiontreedict(I)(f₁ => Fsymbol(c, c, c, c, c, c)[1, 1, 1, 1]) end # ELEMENTARY DUALITY MANIPULATIONS: A- and B-moves @@ -236,7 +236,7 @@ end # -> A-move (foldleft, foldright) is complicated, needs to be reexpressed in standard form # change to N₁ - 1, N₂ + 1 -function bendright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} +function bendright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I<:Sector,N₁,N₂} # map final splitting vertex (a, b)<-c to fusion vertex a<-(c, dual(b)) @assert N₁ > 0 c = f₁.coupled @@ -260,13 +260,13 @@ function bendright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where { end vertices2 = N₂ > 0 ? (f₂.vertices..., nothing) : () f₂′ = FusionTree(uncoupled2, a, isdual2, inner2, vertices2) - return SingletonDict( (f₁′, f₂′) => coeff ) + return SingletonDict((f₁′, f₂′) => coeff) else local newtrees Bmat = Bsymbol(a, b, c) μ = N₁ > 1 ? f₁.vertices[end] : 1 - for ν = 1:size(Bmat, 2) - coeff = sqrtdim(c) * isqrtdim(a) * Bmat[μ,ν] + for ν in 1:size(Bmat, 2) + coeff = sqrtdim(c) * isqrtdim(a) * Bmat[μ, ν] iszero(coeff) && continue if f₁.isdual[N₁] coeff *= conj(frobeniusschur(dual(b))) @@ -276,21 +276,22 @@ function bendright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where { if @isdefined newtrees push!(newtrees, (f₁′, f₂′) => coeff) else - newtrees = FusionTreeDict( (f₁′, f₂′) => coeff ) + newtrees = FusionTreeDict((f₁′, f₂′) => coeff) end end return newtrees end end # change to N₁ + 1, N₂ - 1 -function bendleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where I +function bendleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I} # map final fusion vertex c<-(a, b) to splitting vertex (c, dual(b))<-a - return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) for - ((f₂′, f₁′), coeff) in bendright(f₂, f₁)) + return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) + for + ((f₂′, f₁′), coeff) in bendright(f₂, f₁)) end # change to N₁ - 1, N₂ + 1 -function foldright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {I<:Sector, N₁, N₂} +function foldright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I<:Sector,N₁,N₂} # map first splitting vertex (a, b)<-c to fusion vertex b<-(dual(a), c) @assert N₁ > 0 a = f₁.uncoupled[1] @@ -333,9 +334,10 @@ function foldright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where { for (fr, coeff2) in insertat(fc, 2, f₂) coeff = factor * coeff1 * coeff2 if (@isdefined newtrees) - newtrees[(fl,fr)] = get(newtrees, (fl, fr), zero(coeff)) + coeff + newtrees[(fl, fr)] = get(newtrees, (fl, fr), zero(coeff)) + + coeff else - newtrees = fusiontreedict(I)((fl,fr)=>coeff) + newtrees = fusiontreedict(I)((fl, fr) => coeff) end end end @@ -345,13 +347,13 @@ function foldright(f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where { end end # change to N₁ + 1, N₂ - 1 -function foldleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where I +function foldleft(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I} # map first fusion vertex c<-(a, b) to splitting vertex (dual(a), c)<-b - return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) for - ((f₂′, f₁′), coeff) in foldright(f₂, f₁)) + return fusiontreedict(I)((f₁′, f₂′) => conj(coeff) + for + ((f₂′, f₁′), coeff) in foldright(f₂, f₁)) end - # COMPOSITE DUALITY MANIPULATIONS PART 1: Repartition and transpose #------------------------------------------------------------------- # -> composite manipulations that depend on the duality (rigidity) and pivotal structure @@ -360,8 +362,8 @@ end # one-argument version: check whether `p` is a cyclic permutation (of `1:length(p)`) function iscyclicpermutation(p) N = length(p) - @inbounds for i = 1:N - p[mod1(i+1, N)] == mod1(p[i] + 1, N) || return false + @inbounds for i in 1:N + p[mod1(i + 1, N)] == mod1(p[i] + 1, N) || return false end return true end @@ -379,9 +381,9 @@ function cycleclockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<:Sect for ((f1b, f2b), coeffb) in bendleft(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) - newtrees[(f1b,f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff + newtrees[(f1b, f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff else - newtrees = fusiontreedict(I)((f1b,f2b)=>coeff) + newtrees = fusiontreedict(I)((f1b, f2b) => coeff) end end end @@ -390,9 +392,9 @@ function cycleclockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<:Sect for ((f1b, f2b), coeffb) in foldright(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) - newtrees[(f1b,f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff + newtrees[(f1b, f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff else - newtrees = fusiontreedict(I)((f1b,f2b)=>coeff) + newtrees = fusiontreedict(I)((f1b, f2b) => coeff) end end end @@ -408,9 +410,9 @@ function cycleanticlockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<: for ((f1b, f2b), coeffb) in bendright(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) - newtrees[(f1b,f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff + newtrees[(f1b, f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff else - newtrees = fusiontreedict(I)((f1b,f2b)=>coeff) + newtrees = fusiontreedict(I)((f1b, f2b) => coeff) end end end @@ -419,9 +421,9 @@ function cycleanticlockwise(f₁::FusionTree{I}, f₂::FusionTree{I}) where {I<: for ((f1b, f2b), coeffb) in foldleft(f1a, f2a) coeff = coeffa * coeffb if (@isdefined newtrees) - newtrees[(f1b,f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff + newtrees[(f1b, f2b)] = get(newtrees, (f1b, f2b), zero(coeff)) + coeff else - newtrees = fusiontreedict(I)((f1b,f2b)=>coeff) + newtrees = fusiontreedict(I)((f1b, f2b) => coeff) end end end @@ -441,35 +443,35 @@ outgoing (`f₁`) and incoming sectors (`f₂`) respectively (with identical cou repartitioning the tree by bending incoming to outgoing sectors (or vice versa) in order to have `N` outgoing sectors. """ -@inline function repartition(f₁::FusionTree{I, N₁}, - f₂::FusionTree{I, N₂}, - N::Int) where {I<:Sector, N₁, N₂} +@inline function repartition(f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}, + N::Int) where {I<:Sector,N₁,N₂} f₁.coupled == f₂.coupled || throw(SectorMismatch()) - @assert 0 <= N <= N₁+N₂ + @assert 0 <= N <= N₁ + N₂ return _recursive_repartition(f₁, f₂, Val(N)) end -function _recursive_repartition(f₁::FusionTree{I, N₁}, - f₂::FusionTree{I, N₂}, - ::Val{N}) where {I<:Sector, N₁, N₂, N} +function _recursive_repartition(f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}, + ::Val{N}) where {I<:Sector,N₁,N₂,N} # recursive definition is only way to get correct number of loops for # GenericFusion, but is too complex for type inference to handle, so we # precompute the parameters of the return type F₁ = fusiontreetype(I, N) F₂ = fusiontreetype(I, N₁ + N₂ - N) - coeff = @inbounds Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1,1,1,1] + coeff = @inbounds Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1] T = typeof(coeff) if N == N₁ - return fusiontreedict(I){Tuple{F₁, F₂}, T}( (f₁, f₂) => coeff) + return fusiontreedict(I){Tuple{F₁,F₂},T}((f₁, f₂) => coeff) else - local newtrees::fusiontreedict(I){Tuple{F₁, F₂}, T} + local newtrees::fusiontreedict(I){Tuple{F₁,F₂},T} for ((f₁′, f₂′), coeff1) in (N < N₁ ? bendright(f₁, f₂) : bendleft(f₁, f₂)) for ((f₁′′, f₂′′), coeff2) in _recursive_repartition(f₁′, f₂′, Val(N)) if (@isdefined newtrees) - push!(newtrees, (f₁′′, f₂′′) => coeff1*coeff2) + push!(newtrees, (f₁′′, f₂′′) => coeff1 * coeff2) else - newtrees = - fusiontreedict(I){Tuple{F₁, F₂}, T}((f₁′′, f₂′′) => coeff1*coeff2) + newtrees = fusiontreedict(I){Tuple{F₁,F₂},T}((f₁′′, f₂′′) => coeff1 * + coeff2) end end end @@ -478,7 +480,7 @@ function _recursive_repartition(f₁::FusionTree{I, N₁}, end # transpose double fusion tree -const transposecache = LRU{Any, Any}(; maxsize = 10^5) +const transposecache = LRU{Any,Any}(; maxsize=10^5) const usetransposecache = Ref{Bool}(true) """ @@ -494,7 +496,7 @@ repartitioning and permuting the tree such that sectors `p1` become outgoing and `p2` become incoming. """ function Base.transpose(f₁::FusionTree{I}, f₂::FusionTree{I}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector,N₁,N₂} N = N₁ + N₂ @assert length(f₁) + length(f₂) == N p = linearizepermutation(p1, p2, length(f₁), length(f₂)) @@ -504,24 +506,24 @@ function Base.transpose(f₁::FusionTree{I}, f₂::FusionTree{I}, T = eltype(Fsymbol(u, u, u, u, u, u)) F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) - D = fusiontreedict(I){Tuple{F₁, F₂}, T} + D = fusiontreedict(I){Tuple{F₁,F₂},T} return _get_transpose(D, (f₁, f₂, p1, p2)) else return _transpose((f₁, f₂, p1, p2)) end end -@noinline function _get_transpose(::Type{D}, @nospecialize(key)) where D +@noinline function _get_transpose(::Type{D}, @nospecialize(key)) where {D} d::D = get!(transposecache, key) do - _transpose(key) + return _transpose(key) end return d end -const TransposeKey{I<:Sector, N₁, N₂} = Tuple{<:FusionTree{I}, <:FusionTree{I}, - IndexTuple{N₁}, IndexTuple{N₂}} +const TransposeKey{I<:Sector,N₁,N₂} = Tuple{<:FusionTree{I},<:FusionTree{I}, + IndexTuple{N₁},IndexTuple{N₂}} -function _transpose((f₁, f₂, p1, p2)::TransposeKey{I,N₁,N₂}) where {I<:Sector, N₁, N₂} +function _transpose((f₁, f₂, p1, p2)::TransposeKey{I,N₁,N₂}) where {I<:Sector,N₁,N₂} N = N₁ + N₂ p = linearizepermutation(p1, p2, length(f₁), length(f₂)) newtrees = repartition(f₁, f₂, N₁) @@ -569,9 +571,8 @@ end # -> planar manipulations that do not require braiding, everything is in Fsymbol (A/Bsymbol) function planar_trace(f₁::FusionTree{I}, f₂::FusionTree{I}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, - q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector, N₁, N₂, N₃} - + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}, + q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N₁,N₂,N₃} N = N₁ + N₂ + 2N₃ @assert length(f₁) + length(f₂) == N if N₃ == 0 @@ -579,21 +580,20 @@ function planar_trace(f₁::FusionTree{I}, f₂::FusionTree{I}, end linearindex = (ntuple(identity, Val(length(f₁)))..., - reverse(length(f₁) .+ ntuple(identity, Val(length(f₂))))...) - + reverse(length(f₁) .+ ntuple(identity, Val(length(f₂))))...) q1′ = TupleTools.getindices(linearindex, q1) q2′ = TupleTools.getindices(linearindex, q2) p1′, p2′ = let q′ = (q1′..., q2′...) - (map(l-> l - count(l .> q′), TupleTools.getindices(linearindex, p1)), - map(l-> l - count(l .> q′), TupleTools.getindices(linearindex, p2))) + (map(l -> l - count(l .> q′), TupleTools.getindices(linearindex, p1)), + map(l -> l - count(l .> q′), TupleTools.getindices(linearindex, p2))) end u = one(I) T = typeof(Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) - newtrees = FusionTreeDict{Tuple{F₁,F₂}, T}() + newtrees = FusionTreeDict{Tuple{F₁,F₂},T}() for ((f₁′, f₂′), coeff′) in repartition(f₁, f₂, N) for (f₁′′, coeff′′) in planar_trace(f₁′, q1′, q2′) for (f12′′′, coeff′′′) in transpose(f₁′′, f₂′, p1′, p2′) @@ -608,16 +608,14 @@ function planar_trace(f₁::FusionTree{I}, f₂::FusionTree{I}, end function planar_trace(f::FusionTree{I,N}, - q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector, N, N₃} - - + q1::IndexTuple{N₃}, q2::IndexTuple{N₃}) where {I<:Sector,N,N₃} u = one(I) T = typeof(Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) - F = fusiontreetype(I, N - 2*N₃) + F = fusiontreetype(I, N - 2 * N₃) newtrees = FusionTreeDict{F,T}() - N₃ === 0 && return push!(newtrees, f=>one(T)) + N₃ === 0 && return push!(newtrees, f => one(T)) - for (i,j) in zip(q1, q2) + for (i, j) in zip(q1, q2) (f.uncoupled[i] == dual(f.uncoupled[j]) && f.isdual[i] != f.isdual[j]) || return newtrees end @@ -639,10 +637,10 @@ function planar_trace(f::FusionTree{I,N}, k > N₃ && throw(ArgumentError("Not a planar trace")) q1′ = let i = i, j = j - map(l->(l - (l>i) - (l>j)), TupleTools.deleteat(q1, k)) + map(l -> (l - (l > i) - (l > j)), TupleTools.deleteat(q1, k)) end q2′ = let i = i, j = j - map(l->(l - (l>i) - (l>j)), TupleTools.deleteat(q2, k)) + map(l -> (l - (l > i) - (l > j)), TupleTools.deleteat(q2, k)) end for (f′, coeff′) in elementary_trace(f, i) for (f′′, coeff′′) in planar_trace(f′, q1′, q2′) @@ -656,18 +654,18 @@ function planar_trace(f::FusionTree{I,N}, end # trace two neighbouring indices of a single fusion tree -function elementary_trace(f::FusionTree{I, N}, i) where {I<:Sector, N} +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")) i < N || f.coupled == one(I) || throw(ArgumentError("Cannot trace outputs i=$N and 1 of fusion tree that couples to non-trivial sector")) u = one(I) - T = typeof(Fsymbol(u,u,u,u,u,u)[1,1,1,1]) - F = fusiontreetype(I, N-2) + T = typeof(Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) + F = fusiontreetype(I, N - 2) newtrees = FusionTreeDict{F,T}() - j = mod1(i+1, N) + j = mod1(i + 1, N) b = f.uncoupled[i] b′ = f.uncoupled[j] # if trace is zero, return empty dict @@ -675,31 +673,31 @@ function elementary_trace(f::FusionTree{I, N}, i) where {I<:Sector, N} if i < N inner_extended = (one(I), f.uncoupled[1], f.innerlines..., f.coupled) a = inner_extended[i] - d = inner_extended[i+2] + d = inner_extended[i + 2] a == d || return newtrees - uncoupled′ = TupleTools.deleteat(TupleTools.deleteat(f.uncoupled, i+1), i) - isdual′ = TupleTools.deleteat(TupleTools.deleteat(f.isdual, i+1), i) + uncoupled′ = TupleTools.deleteat(TupleTools.deleteat(f.uncoupled, i + 1), i) + isdual′ = TupleTools.deleteat(TupleTools.deleteat(f.isdual, i + 1), i) coupled′ = f.coupled if N <= 4 inner′ = () else inner′ = i <= 2 ? Base.tail(Base.tail(f.innerlines)) : - TupleTools.deleteat(TupleTools.deleteat(f.innerlines, i-1), i-2) + TupleTools.deleteat(TupleTools.deleteat(f.innerlines, i - 1), i - 2) end if N <= 3 vertices′ = () else vertices′ = i <= 2 ? Base.tail(Base.tail(f.vertices)) : - TupleTools.deleteat(TupleTools.deleteat(f.vertices, i), i-1) + TupleTools.deleteat(TupleTools.deleteat(f.vertices, i), i - 1) end f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner′, vertices′) coeff = sqrtdim(b) if i > 1 - c = f.innerlines[i-1] + c = f.innerlines[i - 1] if FusionStyle(I) isa MultiplicityFreeFusion coeff *= Fsymbol(a, b, dual(b), a, c, one(I)) else - μ = f.vertices[i-1] + μ = f.vertices[i - 1] ν = f.vertices[i] coeff *= Fsymbol(a, b, dual(b), a, c, one(I))[μ, ν, 1, 1] end @@ -727,7 +725,7 @@ function elementary_trace(f::FusionTree{I, N}, i) where {I<:Sector, N} vertices_ = Base.front(f.vertices) f_ = FusionTree(uncoupled_, coupled_, isdual_, inner_, vertices_) fs = FusionTree((b,), b, (!f.isdual[1],), (), ()) - for (f_′, coeff) = merge(fs, f_, one(I), 1) + for (f_′, coeff) in merge(fs, f_, one(I), 1) f_′.innerlines[1] == one(I) || continue uncoupled′ = Base.tail(Base.tail(f_′.uncoupled)) isdual′ = Base.tail(Base.tail(f_′.isdual)) @@ -761,35 +759,36 @@ applying `artin_braid(f′, i; inv = true)` to all the outputs `f′` of tree with non-zero coefficient, namely `f` with coefficient `1`. This keyword has no effect if `BraidingStyle(sectortype(f)) isa SymmetricBraiding`. """ -function artin_braid(f::FusionTree{I, N}, i; inv::Bool = false) where {I<:Sector, N} +function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} 1 <= i < N || throw(ArgumentError("Cannot swap outputs i=$i and i+1 out of only $N outputs")) uncoupled = f.uncoupled - a, b = uncoupled[i], uncoupled[i+1] + a, b = uncoupled[i], uncoupled[i + 1] uncoupled′ = TupleTools.setindex(uncoupled, b, i) - uncoupled′ = TupleTools.setindex(uncoupled′, a, i+1) + uncoupled′ = TupleTools.setindex(uncoupled′, a, i + 1) coupled′ = f.coupled - isdual′ = TupleTools.setindex(f.isdual, f.isdual[i], i+1) - isdual′ = TupleTools.setindex(isdual′, f.isdual[i+1], i) + isdual′ = TupleTools.setindex(f.isdual, f.isdual[i], i + 1) + isdual′ = TupleTools.setindex(isdual′, f.isdual[i + 1], i) inner = f.innerlines inner_extended = (uncoupled[1], inner..., coupled′) vertices = f.vertices u = one(I) if BraidingStyle(I) isa NoBraiding - oneT = Fsymbol(u,u,u,u,u,u)[1,1,1,1] + oneT = Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1] else - oneT = Rsymbol(u,u,u)[1,1] * Fsymbol(u,u,u,u,u,u)[1,1,1,1] + oneT = Rsymbol(u, u, u)[1, 1] * Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1] end - if u in (uncoupled[i], uncoupled[i+1]) + if u in (uncoupled[i], uncoupled[i + 1]) # braiding with trivial sector: simple and always possible inner′ = inner vertices′ = vertices if i > 1 # we also need to alter innerlines and vertices - inner′ = TupleTools.setindex(inner, inner_extended[a == u ? (i+1) : (i-1)], i-1) - vertices′ = TupleTools.setindex(vertices′, vertices[i], i-1) - vertices′ = TupleTools.setindex(vertices′, vertices[i-1], i) + inner′ = TupleTools.setindex(inner, inner_extended[a == u ? (i + 1) : (i - 1)], + i - 1) + vertices′ = TupleTools.setindex(vertices′, vertices[i], i - 1) + vertices′ = TupleTools.setindex(vertices′, vertices[i - 1], i) end f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner′, vertices′) return fusiontreedict(I)(f′ => oneT) @@ -808,8 +807,8 @@ function artin_braid(f::FusionTree{I, N}, i; inv::Bool = false) where {I<:Sector μ = vertices[1] Rmat = inv ? Rsymbol(b, a, c)' : Rsymbol(a, b, c) local newtrees - for ν = 1:size(Rmat, 2) - R = oftype(oneT, Rmat[μ,ν]) + for ν in 1:size(Rmat, 2) + R = oftype(oneT, Rmat[μ, ν]) iszero(R) && continue vertices′ = TupleTools.setindex(vertices, ν, 1) f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner, vertices′) @@ -824,30 +823,36 @@ function artin_braid(f::FusionTree{I, N}, i; inv::Bool = false) where {I<:Sector end # case i > 1: other naming convention b = uncoupled[i] - d = uncoupled[i+1] - a = inner_extended[i-1] + d = uncoupled[i + 1] + a = inner_extended[i - 1] c = inner_extended[i] - e = inner_extended[i+1] + e = inner_extended[i + 1] if FusionStyle(I) isa UniqueFusion c′ = first(a ⊗ d) - coeff = oftype(oneT, if inv - conj(Rsymbol(d, c, e)*Fsymbol(d, a, b, e, c′, c))*Rsymbol(d, a, c′) - else - Rsymbol(c, d, e)*conj(Fsymbol(d, a, b, e, c′, c)*Rsymbol(a, d, c′)) - end) - inner′ = TupleTools.setindex(inner, c′, i-1) + coeff = oftype(oneT, + if inv + conj(Rsymbol(d, c, e) * Fsymbol(d, a, b, e, c′, c)) * + Rsymbol(d, a, c′) + else + Rsymbol(c, d, e) * + conj(Fsymbol(d, a, b, e, c′, c) * Rsymbol(a, d, c′)) + end) + inner′ = TupleTools.setindex(inner, c′, i - 1) f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner′) return fusiontreedict(I)(f′ => coeff) elseif FusionStyle(I) isa SimpleFusion local newtrees for c′ in intersect(a ⊗ d, e ⊗ conj(b)) - coeff = oftype(oneT, if inv - conj(Rsymbol(d, c, e)*Fsymbol(d, a, b, e, c′, c))*Rsymbol(d, a, c′) - else - Rsymbol(c, d, e)*conj(Fsymbol(d, a, b, e, c′, c)*Rsymbol(a, d, c′)) - end) + coeff = oftype(oneT, + if inv + conj(Rsymbol(d, c, e) * Fsymbol(d, a, b, e, c′, c)) * + Rsymbol(d, a, c′) + else + Rsymbol(c, d, e) * + conj(Fsymbol(d, a, b, e, c′, c) * Rsymbol(a, d, c′)) + end) iszero(coeff) && continue - inner′ = TupleTools.setindex(inner, c′, i-1) + inner′ = TupleTools.setindex(inner, c′, i - 1) f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner′) if (@isdefined newtrees) push!(newtrees, f′ => coeff) @@ -862,18 +867,18 @@ function artin_braid(f::FusionTree{I, N}, i; inv::Bool = false) where {I<:Sector Rmat1 = inv ? Rsymbol(d, c, e)' : Rsymbol(c, d, e) Rmat2 = inv ? Rsymbol(d, a, c′)' : Rsymbol(a, d, c′) Fmat = Fsymbol(d, a, b, e, c′, c) - μ = vertices[i-1] + μ = vertices[i - 1] ν = vertices[i] - for σ = 1:Nsymbol(a, d, c′) - for λ = 1:Nsymbol(c′, b, e) + for σ in 1:Nsymbol(a, d, c′) + for λ in 1:Nsymbol(c′, b, e) coeff = zero(oneT) - for ρ = 1:Nsymbol(d, c, e), κ = 1:Nsymbol(d, a, c′) - coeff += Rmat1[ν,ρ]*conj(Fmat[κ,λ,μ,ρ])*conj(Rmat2[σ,κ]) + for ρ in 1:Nsymbol(d, c, e), κ in 1:Nsymbol(d, a, c′) + coeff += Rmat1[ν, ρ] * conj(Fmat[κ, λ, μ, ρ]) * conj(Rmat2[σ, κ]) end iszero(coeff) && continue - vertices′ = TupleTools.setindex(vertices, σ, i-1) + vertices′ = TupleTools.setindex(vertices, σ, i - 1) vertices′ = TupleTools.setindex(vertices′, λ, i) - inner′ = TupleTools.setindex(inner, c′, i-1) + inner′ = TupleTools.setindex(inner, c′, i - 1) f′ = FusionTree{I}(uncoupled′, coupled′, isdual′, inner′, vertices′) if (@isdefined newtrees) push!(newtrees, f′ => coeff) @@ -902,14 +907,14 @@ that if `i` and `j` cross, ``τ_{i,j}`` is applied if `levels[i] < levels[j]` an ``τ_{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. """ -function braid(f::FusionTree{I, N}, - levels::NTuple{N, Int}, - p::NTuple{N, Int}) where {I<:Sector, N} +function braid(f::FusionTree{I,N}, + levels::NTuple{N,Int}, + p::NTuple{N,Int}) where {I<:Sector,N} TupleTools.isperm(p) || throw(ArgumentError("not a valid permutation: $p")) if FusionStyle(I) isa UniqueFusion && BraidingStyle(I) isa SymmetricBraiding coeff = Rsymbol(one(I), one(I), one(I)) - for i = 1:N - for j = 1:i-1 + for i in 1:N + for j in 1:(i - 1) if p[j] > p[i] a, b = f.uncoupled[p[j]], f.uncoupled[p[i]] coeff *= Rsymbol(a, b, first(a ⊗ b)) @@ -922,19 +927,19 @@ function braid(f::FusionTree{I, N}, f′ = FusionTree{I}(uncoupled′, coupled′, isdual′) return fusiontreedict(I)(f′ => coeff) else - coeff = Rsymbol(one(I), one(I), one(I))[1,1] + coeff = Rsymbol(one(I), one(I), one(I))[1, 1] trees = FusionTreeDict(f => coeff) newtrees = empty(trees) for s in permutation2swaps(p) - inv = levels[s] > levels[s+1] + inv = levels[s] > levels[s + 1] for (f, c) in trees - for (f′, c′) in artin_braid(f, s; inv = inv) - newtrees[f′] = get(newtrees, f′, zero(coeff)) + c*c′ + for (f′, c′) in artin_braid(f, s; inv=inv) + newtrees[f′] = get(newtrees, f′, zero(coeff)) + c * c′ end end l = levels[s] - levels = TupleTools.setindex(levels, levels[s+1], s) - levels = TupleTools.setindex(levels, l, s+1) + levels = TupleTools.setindex(levels, levels[s + 1], s) + levels = TupleTools.setindex(levels, l, s + 1) trees, newtrees = newtrees, trees empty!(newtrees) end @@ -950,13 +955,13 @@ Perform a permutation of the uncoupled indices of the fusion tree `f` and return as a `<:AbstractDict` of output trees and corresponding coefficients; this requires that `BraidingStyle(sectortype(f)) isa SymmetricBraiding`. """ -function permute(f::FusionTree{I, N}, p::NTuple{N, Int}) where {I<:Sector, N} +function permute(f::FusionTree{I,N}, p::NTuple{N,Int}) where {I<:Sector,N} @assert BraidingStyle(I) isa SymmetricBraiding return braid(f, ntuple(identity, Val(N)), p) end # braid double fusion tree -const braidcache = LRU{Any, Any}(; maxsize = 10^5) +const braidcache = LRU{Any,Any}(; maxsize=10^5) const usebraidcache_abelian = Ref{Bool}(false) const usebraidcache_nonabelian = Ref{Bool}(true) @@ -979,19 +984,19 @@ levels[j]`. This does not allow to encode the most general braid, but a general be obtained by combining such operations. """ function braid(f₁::FusionTree{I}, f₂::FusionTree{I}, - levels1::IndexTuple, levels2::IndexTuple, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} + levels1::IndexTuple, levels2::IndexTuple, + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector,N₁,N₂} @assert length(f₁) + length(f₂) == N₁ + N₂ @assert length(f₁) == length(levels1) && length(f₂) == length(levels2) @assert TupleTools.isperm((p1..., p2...)) if FusionStyle(f₁) isa UniqueFusion && - BraidingStyle(f₁) isa SymmetricBraiding + BraidingStyle(f₁) isa SymmetricBraiding if usebraidcache_abelian[] u = one(I) T = Int F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) - D = SingletonDict{Tuple{F₁, F₂}, T} + D = SingletonDict{Tuple{F₁,F₂},T} return _get_braid(D, (f₁, f₂, levels1, levels2, p1, p2)) else return _braid((f₁, f₂, levels1, levels2, p1, p2)) @@ -999,10 +1004,11 @@ function braid(f₁::FusionTree{I}, f₂::FusionTree{I}, else if usebraidcache_nonabelian[] u = one(I) - T = typeof(sqrtdim(u)*Fsymbol(u, u, u, u, u, u)[1,1,1,1]*Rsymbol(u, u, u)[1,1]) + T = typeof(sqrtdim(u) * Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1] * + Rsymbol(u, u, u)[1, 1]) F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) - D = FusionTreeDict{Tuple{F₁, F₂}, T} + D = FusionTreeDict{Tuple{F₁,F₂},T} return _get_braid(D, (f₁, f₂, levels1, levels2, p1, p2)) else return _braid((f₁, f₂, levels1, levels2, p1, p2)) @@ -1010,18 +1016,18 @@ function braid(f₁::FusionTree{I}, f₂::FusionTree{I}, end end -@noinline function _get_braid(::Type{D}, @nospecialize(key)) where D +@noinline function _get_braid(::Type{D}, @nospecialize(key)) where {D} d::D = get!(braidcache, key) do - _braid(key) + return _braid(key) end return d end -const BraidKey{I<:Sector, N₁, N₂} = Tuple{<:FusionTree{I}, <:FusionTree{I}, - IndexTuple, IndexTuple, - IndexTuple{N₁}, IndexTuple{N₂}} +const BraidKey{I<:Sector,N₁,N₂} = Tuple{<:FusionTree{I},<:FusionTree{I}, + IndexTuple,IndexTuple, + IndexTuple{N₁},IndexTuple{N₂}} -function _braid((f₁, f₂, l1, l2, p1, p2)::BraidKey{I, N₁, N₂}) where {I<:Sector, N₁, N₂} +function _braid((f₁, f₂, l1, l2, p1, p2)::BraidKey{I,N₁,N₂}) where {I<:Sector,N₁,N₂} p = linearizepermutation(p1, p2, length(f₁), length(f₂)) levels = (l1..., reverse(l2)...) local newtrees @@ -1030,9 +1036,9 @@ function _braid((f₁, f₂, l1, l2, p1, p2)::BraidKey{I, N₁, N₂}) where {I< for ((f₁′, f₂′), coeff3) in repartition(f′, f0, N₁) if @isdefined newtrees newtrees[(f₁′, f₂′)] = get(newtrees, (f₁′, f₂′), zero(coeff3)) + - coeff1*coeff2*coeff3 + coeff1 * coeff2 * coeff3 else - newtrees = fusiontreedict(I)( (f₁′, f₂′) => coeff1*coeff2*coeff3 ) + newtrees = fusiontreedict(I)((f₁′, f₂′) => coeff1 * coeff2 * coeff3) end end end @@ -1053,7 +1059,7 @@ repartitioning and permuting the tree such that sectors `p1` become outgoing and `p2` become incoming. """ function permute(f₁::FusionTree{I}, f₂::FusionTree{I}, - p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector, N₁, N₂} + p1::IndexTuple{N₁}, p2::IndexTuple{N₂}) where {I<:Sector,N₁,N₂} @assert BraidingStyle(I) isa SymmetricBraiding levels1 = ntuple(identity, length(f₁)) levels2 = length(f₁) .+ ntuple(identity, length(f₂)) diff --git a/src/sectors/anyons.jl b/src/sectors/anyons.jl index f431aea4..23e8e476 100644 --- a/src/sectors/anyons.jl +++ b/src/sectors/anyons.jl @@ -10,7 +10,7 @@ struct PlanarTrivial <: Sector end Base.IteratorSize(::Type{SectorValues{PlanarTrivial}}) = HasLength() Base.length(::SectorValues{PlanarTrivial}) = 1 -Base.iterate(::SectorValues{PlanarTrivial}, i = 0) = i == 0 ? (PlanarTrivial(), 1) : nothing +Base.iterate(::SectorValues{PlanarTrivial}, i=0) = i == 0 ? (PlanarTrivial(), 1) : nothing function Base.getindex(::SectorValues{PlanarTrivial}, i::Int) return i == 1 ? PlanarTrivial() : throw(BoundsError(values(PlanarTrivial), i)) @@ -44,14 +44,15 @@ struct FibonacciAnyon <: Sector isone::Bool function FibonacciAnyon(s::Symbol) s in (:I, :τ, :tau) || throw(ArgumentError("Unknown FibonacciAnyon $s.")) - new(s === :I) + return new(s === :I) end end Base.IteratorSize(::Type{SectorValues{FibonacciAnyon}}) = HasLength() Base.length(::SectorValues{FibonacciAnyon}) = 2 -Base.iterate(::SectorValues{FibonacciAnyon}, i = 0) = - i == 0 ? (FibonacciAnyon(:I), 1) : (i == 1 ? (FibonacciAnyon(:τ), 2) : nothing) +function Base.iterate(::SectorValues{FibonacciAnyon}, i=0) + return i == 0 ? (FibonacciAnyon(:I), 1) : (i == 1 ? (FibonacciAnyon(:τ), 2) : nothing) +end function Base.getindex(S::SectorValues{FibonacciAnyon}, i) if i == 1 return FibonacciAnyon(:I) @@ -84,7 +85,7 @@ Base.IteratorSize(::Type{FibonacciIterator}) = Base.HasLength() Base.IteratorEltype(::Type{FibonacciIterator}) = Base.HasEltype() Base.length(iter::FibonacciIterator) = (isone(iter.a) || isone(iter.b)) ? 1 : 2 Base.eltype(::Type{FibonacciIterator}) = FibonacciAnyon -function Base.iterate(iter::FibonacciIterator, state = 1) +function Base.iterate(iter::FibonacciIterator, state=1) I = FibonacciAnyon(:I) τ = FibonacciAnyon(:τ) if state == 1 # first iteration @@ -99,8 +100,9 @@ function Base.iterate(iter::FibonacciIterator, state = 1) end end -Nsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon) = - isone(a) + isone(b) + isone(c) != 2 # zero if one tau and two ones +function Nsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon) + return isone(a) + isone(b) + isone(c) != 2 +end # zero if one tau and two ones function Fsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon, d::FibonacciAnyon, e::FibonacciAnyon, f::FibonacciAnyon) @@ -113,11 +115,11 @@ function Fsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon, τ = FibonacciAnyon(:τ) if a == b == c == d == τ if e == f == I - return +1/_goldenratio + return +1 / _goldenratio elseif e == f == τ - return -1/_goldenratio + return -1 / _goldenratio else - return +1/sqrt(_goldenratio) + return +1 / sqrt(_goldenratio) end else return one(_goldenratio) @@ -125,18 +127,18 @@ function Fsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon, end function Rsymbol(a::FibonacciAnyon, b::FibonacciAnyon, c::FibonacciAnyon) - Nsymbol(a, b, c) || return 0*cis(0π/1) + Nsymbol(a, b, c) || return 0 * cis(0π / 1) if isone(a) || isone(b) - return cis(0π/1) + return cis(0π / 1) else - return isone(c) ? cis(4π/5) : cis(-3π/5) + return isone(c) ? cis(4π / 5) : cis(-3π / 5) end end function Base.show(io::IO, a::FibonacciAnyon) s = isone(a) ? ":I" : ":τ" return get(io, :typeinfo, nothing) === FibonacciAnyon ? - print(io, s) : print(io, "FibonacciAnyon(", s, ")") + print(io, s) : print(io, "FibonacciAnyon(", s, ")") end Base.hash(a::FibonacciAnyon, h::UInt) = hash(a.isone, h) @@ -160,7 +162,7 @@ struct IsingAnyon <: Sector if !(s in (:I, :σ, :ψ)) throw(ValueError("Unknown IsingAnyon $s.")) end - new(s) + return new(s) end end @@ -168,7 +170,7 @@ const all_isinganyons = (IsingAnyon(:I), IsingAnyon(:σ), IsingAnyon(:ψ)) Base.IteratorSize(::Type{SectorValues{IsingAnyon}}) = HasLength() Base.length(::SectorValues{IsingAnyon}) = length(all_isinganyons) -Base.iterate(::SectorValues{IsingAnyon}, i = 1) = iterate(all_isinganyons, i) +Base.iterate(::SectorValues{IsingAnyon}, i=1) = iterate(all_isinganyons, i) Base.getindex(S::SectorValues{IsingAnyon}, i) = getindex(all_isinganyons, i) function findindex(::SectorValues{IsingAnyon}, a::IsingAnyon) @@ -203,7 +205,7 @@ function Base.length(iter::IsingIterator) return (iter.a == σ && iter.b == σ) ? 2 : 1 end -function Base.iterate(iter::IsingIterator, state = 1) +function Base.iterate(iter::IsingIterator, state=1) I, σ, ψ = all_isinganyons if state == 1 # first iteration iter.a == I && return (iter.b, 2) @@ -226,8 +228,7 @@ function Nsymbol(a::IsingAnyon, b::IsingAnyon, c::IsingAnyon) || (c == I && a == b) || (a == σ && b == σ && c == ψ) || (a == σ && b == ψ && c == σ) - || (a == ψ && b == σ && c == σ) - ) + || (a == ψ && b == σ && c == σ)) end function Fsymbol(a::IsingAnyon, b::IsingAnyon, c::IsingAnyon, @@ -239,9 +240,9 @@ function Fsymbol(a::IsingAnyon, b::IsingAnyon, c::IsingAnyon, I, σ, ψ = all_isinganyons if a == b == c == d == σ if e == f == ψ - return -1.0/sqrt(2.0) + return -1.0 / sqrt(2.0) else - return 1.0/sqrt(2.0) + return 1.0 / sqrt(2.0) end end if e == f == σ @@ -259,14 +260,14 @@ function Rsymbol(a::IsingAnyon, b::IsingAnyon, c::IsingAnyon) I, σ, ψ = all_isinganyons if c == I if b == a == σ - return cis(-π/8) + return cis(-π / 8) elseif b == a == ψ return complex(-1.0) end elseif c == σ && (a == σ && b == ψ || a == ψ && b == σ) return -1.0im elseif c == ψ && a == b == σ - return cis(3π/8) + return cis(3π / 8) end return complex(1.0) end diff --git a/src/sectors/fermions.jl b/src/sectors/fermions.jl index e78ea498..d42624a8 100644 --- a/src/sectors/fermions.jl +++ b/src/sectors/fermions.jl @@ -1,27 +1,28 @@ struct Fermion{P,I<:Sector} <: Sector sector::I - function Fermion{P,I}(sector::I) where {P, I<:Sector} + function Fermion{P,I}(sector::I) where {P,I<:Sector} @assert BraidingStyle(I) isa Bosonic return new{P,I}(sector) end end -Fermion{P}(sector::I) where {P, I<:Sector} = Fermion{P,I}(sector) -Fermion{P,I}(sector) where {P, I<:Sector} = Fermion{P,I}(convert(I, sector)) -Base.convert(::Type{Fermion{P,I}}, a::Fermion{P,I}) where {P, I<:Sector} = a -Base.convert(::Type{Fermion{P,I}}, a) where {P, I<:Sector} = Fermion{P,I}(convert(I, a)) +Fermion{P}(sector::I) where {P,I<:Sector} = Fermion{P,I}(sector) +Fermion{P,I}(sector) where {P,I<:Sector} = Fermion{P,I}(convert(I, sector)) +Base.convert(::Type{Fermion{P,I}}, a::Fermion{P,I}) where {P,I<:Sector} = a +Base.convert(::Type{Fermion{P,I}}, a) where {P,I<:Sector} = Fermion{P,I}(convert(I, a)) -fermionparity(f::Fermion{P}) where P = P(f.sector) +fermionparity(f::Fermion{P}) where {P} = P(f.sector) -Base.IteratorSize(::Type{SectorValues{Fermion{P,I}}}) where {P, I<:Sector} = - Base.IteratorSize(SectorValues{I}) -Base.length(::SectorValues{Fermion{P,I}}) where {P, I<:Sector} = length(values(I)) -function Base.iterate(::SectorValues{Fermion{P, I}}) where {P, I<:Sector} +function Base.IteratorSize(::Type{SectorValues{Fermion{P,I}}}) where {P,I<:Sector} + return Base.IteratorSize(SectorValues{I}) +end +Base.length(::SectorValues{Fermion{P,I}}) where {P,I<:Sector} = length(values(I)) +function Base.iterate(::SectorValues{Fermion{P,I}}) where {P,I<:Sector} next = iterate(values(I)) @assert next !== nothing value, state = next return Fermion{P}(value), state end -function Base.iterate(::SectorValues{Fermion{P, I}}, state) where {P, I<:Sector} +function Base.iterate(::SectorValues{Fermion{P,I}}, state) where {P,I<:Sector} next = iterate(values(I), state) if next === nothing return nothing @@ -30,12 +31,14 @@ function Base.iterate(::SectorValues{Fermion{P, I}}, state) where {P, I<:Sector} return Fermion{P}(value), state end end -Base.getindex(::SectorValues{Fermion{P, I}}, i) where {P, I<:Sector} = - Fermion{P}(values(I)[i]) -findindex(::SectorValues{Fermion{P, I}}, f::Fermion{P, I}) where {P, I<:Sector} = - findindex(values(I), f.sector) +function Base.getindex(::SectorValues{Fermion{P,I}}, i) where {P,I<:Sector} + return Fermion{P}(values(I)[i]) +end +function findindex(::SectorValues{Fermion{P,I}}, f::Fermion{P,I}) where {P,I<:Sector} + return findindex(values(I), f.sector) +end -Base.one(::Type{Fermion{P, I}}) where {P, I<:Sector} = Fermion{P}(one(I)) +Base.one(::Type{Fermion{P,I}}) where {P,I<:Sector} = Fermion{P}(one(I)) Base.conj(f::Fermion{P}) where {P} = Fermion{P}(conj(f.sector)) dim(f::Fermion) = dim(f.sector) @@ -48,8 +51,9 @@ Base.isreal(::Type{Fermion{<:Any,I}}) where {I<:Sector} = isreal(I) Nsymbol(a::F, b::F, c::F) where {F<:Fermion} = Nsymbol(a.sector, b.sector, c.sector) -Fsymbol(a::F, b::F, c::F, d::F, e::F, f::F) where {F<:Fermion} = - Fsymbol(a.sector, b.sector, c.sector, d.sector, e.sector, f.sector) +function Fsymbol(a::F, b::F, c::F, d::F, e::F, f::F) where {F<:Fermion} + return Fsymbol(a.sector, b.sector, c.sector, d.sector, e.sector, f.sector) +end function Rsymbol(a::F, b::F, c::F) where {F<:Fermion} if fermionparity(a) && fermionparity(b) @@ -59,16 +63,16 @@ function Rsymbol(a::F, b::F, c::F) where {F<:Fermion} end end -twist(a::Fermion) = ifelse(fermionparity(a), -1, +1)*twist(a.sector) +twist(a::Fermion) = ifelse(fermionparity(a), -1, +1) * twist(a.sector) -type_repr(::Type{Fermion{P,I}}) where {P, I<:Sector} = "Fermion{$P, " * type_repr(I) * "}" +type_repr(::Type{Fermion{P,I}}) where {P,I<:Sector} = "Fermion{$P, " * type_repr(I) * "}" -function Base.show(io::IO, a::Fermion{P, I}) where {P, I<:Sector} - if get(io, :typeinfo, nothing) !== Fermion{P, I} +function Base.show(io::IO, a::Fermion{P,I}) where {P,I<:Sector} + if get(io, :typeinfo, nothing) !== Fermion{P,I} print(io, type_repr(typeof(a)), "(") end print(IOContext(io, :typeinfo => I), a.sector) - if get(io, :typeinfo, nothing) !== Fermion{P, I} + if get(io, :typeinfo, nothing) !== Fermion{P,I} print(io, ")") end end @@ -80,9 +84,9 @@ _fermionparity(a::Z2Irrep) = isodd(a.n) _fermionnumber(a::U1Irrep) = isodd(convert(Int, a.charge)) _fermionspin(a::SU2Irrep) = isodd(twice(a.j)) -const FermionParity = Fermion{_fermionparity, Z2Irrep} -const FermionNumber = Fermion{_fermionnumber, U1Irrep} -const FermionSpin = Fermion{_fermionspin, SU2Irrep} +const FermionParity = Fermion{_fermionparity,Z2Irrep} +const FermionNumber = Fermion{_fermionnumber,U1Irrep} +const FermionSpin = Fermion{_fermionspin,SU2Irrep} const fℤ₂ = FermionParity const fU₁ = FermionNumber const fSU₂ = FermionSpin diff --git a/src/sectors/groups.jl b/src/sectors/groups.jl index 4f37ffca..eab121fa 100644 --- a/src/sectors/groups.jl +++ b/src/sectors/groups.jl @@ -25,17 +25,19 @@ abstract type ProductGroup{T<:GroupTuple} <: Group end ×(a::Type{<:Group}, b::Type{<:Group}, c::Type{<:Group}...) = ×(×(a, b), c...) ×(G::Type{<:Group}) = ProductGroup{Tuple{G}} ×(G1::Type{ProductGroup{Tuple{}}}, - G2::Type{ProductGroup{T}}) where {T<:GroupTuple} = G2 -×(G1::Type{ProductGroup{T1}}, - G2::Type{ProductGroup{T2}}) where {T1<:GroupTuple, T2<:GroupTuple} = - tuple_type_head(T1) × (ProductGroup{tuple_type_tail(T1)} × G2) -×(G1::Type{ProductGroup{Tuple{}}}, G2::Type{<:Group}) = - ProductGroup{Tuple{G2}} -×(G1::Type{ProductGroup{T}}, G2::Type{<:Group}) where {T<:GroupTuple} = - Base.tuple_type_head(T) × (ProductGroup{Base.tuple_type_tail(T)} × G2) -×(G1::Type{<:Group}, G2::Type{ProductGroup{T}}) where {T<:GroupTuple} = - ProductGroup{Base.tuple_type_cons(G1, T)} -×(G1::Type{<:Group}, G2::Type{<:Group}) = ProductGroup{Tuple{G1, G2}} +G2::Type{ProductGroup{T}}) where {T<:GroupTuple} = G2 +function ×(G1::Type{ProductGroup{T1}}, + G2::Type{ProductGroup{T2}}) where {T1<:GroupTuple,T2<:GroupTuple} + return tuple_type_head(T1) × (ProductGroup{tuple_type_tail(T1)} × G2) +end +×(G1::Type{ProductGroup{Tuple{}}}, G2::Type{<:Group}) = ProductGroup{Tuple{G2}} +function ×(G1::Type{ProductGroup{T}}, G2::Type{<:Group}) where {T<:GroupTuple} + return Base.tuple_type_head(T) × (ProductGroup{Base.tuple_type_tail(T)} × G2) +end +function ×(G1::Type{<:Group}, G2::Type{ProductGroup{T}}) where {T<:GroupTuple} + return ProductGroup{Base.tuple_type_cons(G1, T)} +end +×(G1::Type{<:Group}, G2::Type{<:Group}) = ProductGroup{Tuple{G1,G2}} function type_repr(G::Type{<:ProductGroup}) T = G.parameters[1] @@ -44,7 +46,7 @@ function type_repr(G::Type{<:ProductGroup}) s = "ProductGroup{Tuple{" * type_repr(groups[1]) * "}}" else s = "(" - for i = 1:length(groups) + for i in 1:length(groups) if i != 1 s *= " × " end diff --git a/src/sectors/irreps.jl b/src/sectors/irreps.jl index 5e2105f5..24d05e90 100644 --- a/src/sectors/irreps.jl +++ b/src/sectors/irreps.jl @@ -28,20 +28,20 @@ construct or obtain a concrete subtype of `AbstractIrrep{G}` that implements the const Irrep = IrrepTable() function type_repr(t::Type{<:AbstractIrrep{G}}) where {G<:Group} - s = "Irrep[" * type_repr(G) * "]" + return s = "Irrep[" * type_repr(G) * "]" end function Base.show(io::IO, c::AbstractIrrep) I = typeof(c) if get(io, :typeinfo, nothing) !== I print(io, type_repr(I), "(") - for k = 1:fieldcount(I) + for k in 1:fieldcount(I) k > 1 && print(io, ", ") print(io, getfield(c, k)) end print(io, ")") else fieldcount(I) > 1 && print(io, "(") - for k = 1:fieldcount(I) + for k in 1:fieldcount(I) k > 1 && print(io, ", ") print(io, getfield(c, k)) end @@ -54,15 +54,17 @@ FusionStyle(::Type{<:AbelianIrrep}) = UniqueFusion() Base.isreal(::Type{<:AbelianIrrep}) = true Nsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = c == first(a ⊗ b) -Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:AbelianIrrep} = - Int(Nsymbol(a, b, e)*Nsymbol(e, c, d)*Nsymbol(b, c, f)*Nsymbol(a, f, d)) +function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:AbelianIrrep} + return Int(Nsymbol(a, b, e) * Nsymbol(e, c, d) * Nsymbol(b, c, f) * Nsymbol(a, f, d)) +end frobeniusschur(a::AbelianIrrep) = 1 Asymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c)) Bsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c)) Rsymbol(a::I, b::I, c::I) where {I<:AbelianIrrep} = Int(Nsymbol(a, b, c)) -fusiontensor(a::I, b::I, c::I) where {I<:AbelianIrrep} = - fill(Int(Nsymbol(a, b, c)), (1, 1, 1, 1)) +function fusiontensor(a::I, b::I, c::I) where {I<:AbelianIrrep} + return fill(Int(Nsymbol(a, b, c)), (1, 1, 1, 1)) +end # ZNIrrep: irreps of Z_N are labelled by integers mod N; do we ever want N > 64? """ @@ -79,26 +81,28 @@ struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}} n::Int8 function ZNIrrep{N}(n::Integer) where {N} @assert N < 64 - new{N}(mod(n, N)) + return new{N}(mod(n, N)) end end -Base.getindex(::IrrepTable, ::Type{ℤ{N}}) where N = ZNIrrep{N} +Base.getindex(::IrrepTable, ::Type{ℤ{N}}) where {N} = ZNIrrep{N} Base.convert(Z::Type{<:ZNIrrep}, n::Real) = Z(n) const Z2Irrep = ZNIrrep{2} const Z3Irrep = ZNIrrep{3} const Z4Irrep = ZNIrrep{4} -Base.one(::Type{ZNIrrep{N}}) where {N} =ZNIrrep{N}(0) +Base.one(::Type{ZNIrrep{N}}) where {N} = ZNIrrep{N}(0) Base.conj(c::ZNIrrep{N}) where {N} = ZNIrrep{N}(-c.n) -⊗(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = (ZNIrrep{N}(c1.n+c2.n),) +⊗(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = (ZNIrrep{N}(c1.n + c2.n),) -Base.IteratorSize(::Type{SectorValues{ZNIrrep{N}}}) where N = HasLength() -Base.length(::SectorValues{ZNIrrep{N}}) where N = N -Base.iterate(::SectorValues{ZNIrrep{N}}, i = 0) where N = - return i == N ? nothing : (ZNIrrep{N}(i), i+1) -Base.getindex(::SectorValues{ZNIrrep{N}}, i::Int) where N = - 1 <= i <= N ? ZNIrrep{N}(i-1) : throw(BoundsError(values(ZNIrrep{N}), i)) -findindex(::SectorValues{ZNIrrep{N}}, c::ZNIrrep{N}) where N = c.n + 1 +Base.IteratorSize(::Type{SectorValues{ZNIrrep{N}}}) where {N} = HasLength() +Base.length(::SectorValues{ZNIrrep{N}}) where {N} = N +function Base.iterate(::SectorValues{ZNIrrep{N}}, i=0) where {N} + return i == N ? nothing : (ZNIrrep{N}(i), i + 1) +end +function Base.getindex(::SectorValues{ZNIrrep{N}}, i::Int) where {N} + return 1 <= i <= N ? ZNIrrep{N}(i - 1) : throw(BoundsError(values(ZNIrrep{N}), i)) +end +findindex(::SectorValues{ZNIrrep{N}}, c::ZNIrrep{N}) where {N} = c.n + 1 Base.hash(c::ZNIrrep{N}, h::UInt) where {N} = hash(c.n, h) Base.isless(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = isless(c1.n, c2.n) @@ -122,27 +126,33 @@ Base.convert(::Type{U1Irrep}, c::Real) = U1Irrep(c) Base.one(::Type{U1Irrep}) = U1Irrep(0) Base.conj(c::U1Irrep) = U1Irrep(-c.charge) -⊗(c1::U1Irrep, c2::U1Irrep) = (U1Irrep(c1.charge+c2.charge),) +⊗(c1::U1Irrep, c2::U1Irrep) = (U1Irrep(c1.charge + c2.charge),) Base.IteratorSize(::Type{SectorValues{U1Irrep}}) = IsInfinite() -Base.iterate(::SectorValues{U1Irrep}, i = 0) = +function Base.iterate(::SectorValues{U1Irrep}, i=0) return i <= 0 ? (U1Irrep(half(i)), (-i + 1)) : (U1Irrep(half(i)), -i) +end function Base.getindex(::SectorValues{U1Irrep}, i::Int) i < 1 && throw(BoundsError(values(U1Irrep), i)) - return U1Irrep(iseven(i) ? half(i>>1) : -half(i>>1)) + return U1Irrep(iseven(i) ? half(i >> 1) : -half(i >> 1)) +end +function findindex(::SectorValues{U1Irrep}, c::U1Irrep) + return (n = twice(c.charge); 2 * abs(n) + (n <= 0)) end -findindex(::SectorValues{U1Irrep}, c::U1Irrep) = (n = twice(c.charge); 2*abs(n)+(n<=0)) Base.hash(c::U1Irrep, h::UInt) = hash(c.charge, h) -@inline Base.isless(c1::U1Irrep, c2::U1Irrep) = - isless(abs(c1.charge), abs(c2.charge)) || zero(HalfInt) < c1.charge == -c2.charge +@inline function Base.isless(c1::U1Irrep, c2::U1Irrep) + return isless(abs(c1.charge), abs(c2.charge)) || zero(HalfInt) < c1.charge == -c2.charge +end # Non-abelian groups #------------------------------------------------------------------------------# # SU2Irrep: irreps of SU2 are labelled by half integers j struct SU2IrrepException <: Exception end -Base.show(io::IO, ::SU2IrrepException) = - print(io, "Irreps of (bosonic or fermionic) `SU₂` should be labelled by non-negative half integers, i.e. elements of `Rational{Int}` with denominator 1 or 2") +function Base.show(io::IO, ::SU2IrrepException) + return print(io, + "Irreps of (bosonic or fermionic) `SU₂` should be labelled by non-negative half integers, i.e. elements of `Rational{Int}` with denominator 1 or 2") +end """ SU2Irrep(j::Real) @@ -156,7 +166,7 @@ struct SU2Irrep <: AbstractIrrep{SU₂} j::HalfInt function SU2Irrep(j) j >= zero(j) || error("Not a valid SU₂ irrep") - new(j) + return new(j) end end Base.getindex(::IrrepTable, ::Type{SU₂}) = SU2Irrep @@ -165,40 +175,43 @@ Base.convert(::Type{SU2Irrep}, j::Real) = SU2Irrep(j) const _su2one = SU2Irrep(zero(HalfInt)) Base.one(::Type{SU2Irrep}) = _su2one Base.conj(s::SU2Irrep) = s -⊗(s1::SU2Irrep, s2::SU2Irrep) = SectorSet{SU2Irrep}(abs(s1.j-s2.j):(s1.j+s2.j)) +⊗(s1::SU2Irrep, s2::SU2Irrep) = SectorSet{SU2Irrep}(abs(s1.j - s2.j):(s1.j + s2.j)) Base.IteratorSize(::Type{SectorValues{SU2Irrep}}) = IsInfinite() -Base.iterate(::SectorValues{SU2Irrep}, i = 0) = (SU2Irrep(half(i)), i+1) -Base.getindex(::SectorValues{SU2Irrep}, i::Int) = - 1 <= i ? SU2Irrep(half(i-1)) : throw(BoundsError(values(SU2Irrep), i)) -findindex(::SectorValues{SU2Irrep}, s::SU2Irrep) = twice(s.j)+1 +Base.iterate(::SectorValues{SU2Irrep}, i=0) = (SU2Irrep(half(i)), i + 1) +function Base.getindex(::SectorValues{SU2Irrep}, i::Int) + return 1 <= i ? SU2Irrep(half(i - 1)) : throw(BoundsError(values(SU2Irrep), i)) +end +findindex(::SectorValues{SU2Irrep}, s::SU2Irrep) = twice(s.j) + 1 -dim(s::SU2Irrep) = twice(s.j)+1 +dim(s::SU2Irrep) = twice(s.j) + 1 FusionStyle(::Type{SU2Irrep}) = SimpleFusion() Base.isreal(::Type{SU2Irrep}) = true Nsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep) = WignerSymbols.δ(sa.j, sb.j, sc.j) function Fsymbol(s1::SU2Irrep, s2::SU2Irrep, s3::SU2Irrep, - s4::SU2Irrep, s5::SU2Irrep, s6::SU2Irrep) + s4::SU2Irrep, s5::SU2Irrep, s6::SU2Irrep) if all(==(_su2one), (s1, s2, s3, s4, s5, s6)) return 1.0 else - return sqrtdim(s5) * sqrtdim(s6) * WignerSymbols.racahW(Float64, s1.j, s2.j, - s4.j, s3.j, s5.j, s6.j) + return sqrtdim(s5) * sqrtdim(s6) * + WignerSymbols.racahW(Float64, s1.j, s2.j, + s4.j, s3.j, s5.j, s6.j) end end function Rsymbol(sa::SU2Irrep, sb::SU2Irrep, sc::SU2Irrep) - Nsymbol(sa, sb, sc) || return 0. - iseven(convert(Int, sa.j+sb.j-sc.j)) ? 1.0 : -1.0 + Nsymbol(sa, sb, sc) || return 0.0 + return iseven(convert(Int, sa.j + sb.j - sc.j)) ? 1.0 : -1.0 end function fusiontensor(a::SU2Irrep, b::SU2Irrep, c::SU2Irrep) C = Array{Float64}(undef, dim(a), dim(b), dim(c), 1) ja, jb, jc = a.j, b.j, c.j - for kc = 1:dim(c), kb = 1:dim(b), ka = 1:dim(a) - C[ka,kb,kc,1] = WignerSymbols.clebschgordan(ja, ja+1-ka, jb, jb+1-kb, jc, jc+1-kc) + for kc in 1:dim(c), kb in 1:dim(b), ka in 1:dim(a) + C[ka, kb, kc, 1] = WignerSymbols.clebschgordan(ja, ja + 1 - ka, jb, jb + 1 - kb, jc, + jc + 1 - kc) end return C end @@ -225,7 +238,7 @@ struct CU1Irrep <: AbstractIrrep{CU₁} # if j == 0, s = 0 (trivial) or s = 1 (non-trivial), # else s = 2 (two-dimensional representation) # Let constructor take the actual half integer value j - function CU1Irrep(j::Real, s::Integer = ifelse(j>zero(j), 2, 0)) + function CU1Irrep(j::Real, s::Integer=ifelse(j > zero(j), 2, 0)) if ((j > zero(j) && s == 2) || (j == zero(j) && (s == 0 || s == 1))) new(j, s) else @@ -237,14 +250,14 @@ Base.getindex(::IrrepTable, ::Type{CU₁}) = CU1Irrep Base.convert(::Type{CU1Irrep}, (j, s)::Tuple{Real,Integer}) = CU1Irrep(j, s) Base.IteratorSize(::Type{SectorValues{CU1Irrep}}) = IsInfinite() -function Base.iterate(::SectorValues{CU1Irrep}, state = (0, 0)) +function Base.iterate(::SectorValues{CU1Irrep}, state=(0, 0)) j, s = state if iszero(j) && s == 0 return CU1Irrep(j, s), (j, 1) elseif iszero(j) && s == 1 - return CU1Irrep(j, s), (j+1, 2) + return CU1Irrep(j, s), (j + 1, 2) else - return CU1Irrep(half(j), s), (j+1, 2) + return CU1Irrep(half(j), s), (j + 1, 2) end end function Base.getindex(::SectorValues{CU1Irrep}, i::Int) @@ -254,19 +267,20 @@ function Base.getindex(::SectorValues{CU1Irrep}, i::Int) elseif i == 2 return CU1Irrep(0, 1) else - return CU1Irrep(half(i-2), 2) + return CU1Irrep(half(i - 2), 2) end end findindex(::SectorValues{CU1Irrep}, c::CU1Irrep) = twice(c.j) + iszero(c.j) + c.s Base.hash(c::CU1Irrep, h::UInt) = hash(c.s, hash(c.j, h)) -Base.isless(c1::CU1Irrep, c2::CU1Irrep) = - isless(c1.j, c2.j) || (c1.j == c2.j == zero(HalfInt) && c1.s < c2.s) +function Base.isless(c1::CU1Irrep, c2::CU1Irrep) + return isless(c1.j, c2.j) || (c1.j == c2.j == zero(HalfInt) && c1.s < c2.s) +end # CU1Irrep(j::Real, s::Int = ifelse(j>0, 2, 0)) = CU1Irrep(convert(HalfInteger, j), s) Base.convert(::Type{CU1Irrep}, j::Real) = CU1Irrep(j) -Base.convert(::Type{CU1Irrep}, js::Tuple{Real, Int}) = CU1Irrep(js...) +Base.convert(::Type{CU1Irrep}, js::Tuple{Real,Int}) = CU1Irrep(js...) Base.one(::Type{CU1Irrep}) = CU1Irrep(zero(HalfInt), 0) Base.conj(c::CU1Irrep) = c @@ -275,7 +289,7 @@ struct CU1ProdIterator a::CU1Irrep b::CU1Irrep end -function Base.iterate(p::CU1ProdIterator, s::Int = 1) +function Base.iterate(p::CU1ProdIterator, s::Int=1) if s == 1 if p.a.j == p.b.j == zero(HalfInt) return CU1Irrep(zero(HalfInt), xor(p.a.s, p.b.s)), 4 @@ -286,7 +300,7 @@ function Base.iterate(p::CU1ProdIterator, s::Int = 1) elseif p.a == p.b # != zero return one(CU1Irrep), 2 else - return CU1Irrep(abs(p.a.j - p.b.j)), 3 + return CU1Irrep(abs(p.a.j - p.b.j)), 3 end elseif s == 2 return CU1Irrep(zero(HalfInt), 1), 3 @@ -314,43 +328,43 @@ FusionStyle(::Type{CU1Irrep}) = SimpleFusion() Base.isreal(::Type{CU1Irrep}) = true function Nsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep) - ifelse(c.s == 0, (a.j == b.j) & ((a.s == b.s == 2) | (a.s == b.s)), - ifelse(c.s == 1, (a.j == b.j) & ((a.s == b.s == 2) | (a.s != b.s)), - (c.j == a.j + b.j) | (c.j == abs(a.j - b.j)) )) + return ifelse(c.s == 0, (a.j == b.j) & ((a.s == b.s == 2) | (a.s == b.s)), + ifelse(c.s == 1, (a.j == b.j) & ((a.s == b.s == 2) | (a.s != b.s)), + (c.j == a.j + b.j) | (c.j == abs(a.j - b.j)))) end function Fsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep, - d::CU1Irrep, e::CU1Irrep, f::CU1Irrep) + d::CU1Irrep, e::CU1Irrep, f::CU1Irrep) Nabe = convert(Int, Nsymbol(a, b, e)) Necd = convert(Int, Nsymbol(e, c, d)) Nbcf = convert(Int, Nsymbol(b, c, f)) Nafd = convert(Int, Nsymbol(a, f, d)) - Nabe*Necd*Nbcf*Nafd == 0 && return 0. + Nabe * Necd * Nbcf * Nafd == 0 && return 0.0 op = CU1Irrep(0, 0) om = CU1Irrep(0, 1) if a == op || b == op || c == op - return 1. + return 1.0 end if (a == b == om) || (a == c == om) || (b == c == om) - return 1. + return 1.0 end if a == om if d.j == zero(HalfInt) - return 1. + return 1.0 else - return (d.j == c.j - b.j) ? -1. : 1. + return (d.j == c.j - b.j) ? -1.0 : 1.0 end end if b == om - return (d.j == abs(a.j - c.j)) ? -1. : 1. + return (d.j == abs(a.j - c.j)) ? -1.0 : 1.0 end if c == om - return (d.j == a.j - b.j) ? -1. : 1. + return (d.j == a.j - b.j) ? -1.0 : 1.0 end # from here on, a, b, c are neither 0+ or 0- - s = sqrt(2)/2 + s = sqrt(2) / 2 if a == b == c if d == a if e.j == 0 @@ -360,10 +374,10 @@ function Fsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep, return e.s == 1 ? -s : s end else - return f.j == 0 ? s : 0. + return f.j == 0 ? s : 0.0 end else - return 1. + return 1.0 end end if a == b # != c @@ -374,7 +388,7 @@ function Fsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep, return s end else - return 1. + return 1.0 end end if b == c @@ -385,24 +399,24 @@ function Fsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep, return f.s == 1 ? -s : s end else - return 1. + return 1.0 end end if a == c if d == b if e.j == f.j - return 0. + return 0.0 else - return 1. + return 1.0 end else - return d.s == 1 ? -1. : 1. + return d.s == 1 ? -1.0 : 1.0 end end if d == om - return b.j == a.j + c.j ? -1. : 1. + return b.j == a.j + c.j ? -1.0 : 1.0 end - return 1. + return 1.0 end function Rsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep) R = convert(Float64, Nsymbol(a, b, c)) @@ -410,35 +424,35 @@ function Rsymbol(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep) end function fusiontensor(a::CU1Irrep, b::CU1Irrep, c::CU1Irrep) - C = fill(0., dim(a), dim(b), dim(c), 1) + C = fill(0.0, dim(a), dim(b), dim(c), 1) !Nsymbol(a, b, c) && return C if c.j == 0 if a.j == b.j == 0 - C[1, 1, 1, 1] = 1. + C[1, 1, 1, 1] = 1.0 else if c.s == 0 - C[1, 2, 1, 1] = 1. / sqrt(2) - C[2, 1, 1, 1] = 1. / sqrt(2) + C[1, 2, 1, 1] = 1.0 / sqrt(2) + C[2, 1, 1, 1] = 1.0 / sqrt(2) else - C[1, 2, 1, 1] = 1. / sqrt(2) - C[2, 1, 1, 1] = -1. / sqrt(2) + C[1, 2, 1, 1] = 1.0 / sqrt(2) + C[2, 1, 1, 1] = -1.0 / sqrt(2) end end elseif a.j == 0 - C[1, 1, 1, 1] = 1. - C[1, 2, 2, 1] = a.s == 1 ? -1. : 1. + C[1, 1, 1, 1] = 1.0 + C[1, 2, 2, 1] = a.s == 1 ? -1.0 : 1.0 elseif b.j == 0 - C[1, 1, 1, 1] = 1. - C[2, 1, 2, 1] = b.s == 1 ? -1. : 1. + C[1, 1, 1, 1] = 1.0 + C[2, 1, 2, 1] = b.s == 1 ? -1.0 : 1.0 elseif c.j == a.j + b.j - C[1, 1, 1, 1] = 1. - C[2, 2, 2, 1] = 1. + C[1, 1, 1, 1] = 1.0 + C[2, 2, 2, 1] = 1.0 elseif c.j == a.j - b.j - C[1, 2, 1, 1] = 1. - C[2, 1, 2, 1] = 1. + C[1, 2, 1, 1] = 1.0 + C[2, 1, 2, 1] = 1.0 elseif c.j == b.j - a.j - C[2, 1, 1, 1] = 1. - C[1, 2, 2, 1] = 1. + C[2, 1, 1, 1] = 1.0 + C[1, 2, 2, 1] = 1.0 end return C end diff --git a/src/sectors/product.jl b/src/sectors/product.jl index 178560f7..9344a32d 100644 --- a/src/sectors/product.jl +++ b/src/sectors/product.jl @@ -6,13 +6,16 @@ struct ProductSector{T<:SectorTuple} <: Sector sectors::T end _sectors(::Type{Tuple{}}) = () -Base.@pure _sectors(::Type{T}) where {T<:SectorTuple} = - (Base.tuple_type_head(T), _sectors(Base.tuple_type_tail(T))...) +Base.@pure function _sectors(::Type{T}) where {T<:SectorTuple} + return (Base.tuple_type_head(T), _sectors(Base.tuple_type_tail(T))...) +end -Base.IteratorSize(::Type{SectorValues{ProductSector{T}}}) where {T<:SectorTuple} = - Base.IteratorSize(Base.Iterators.product(map(values, _sectors(T))...)) -Base.size(::SectorValues{ProductSector{T}}) where {T<:SectorTuple} = - map(s->length(values(s)), _sectors(T)) +function Base.IteratorSize(::Type{SectorValues{ProductSector{T}}}) where {T<:SectorTuple} + return Base.IteratorSize(Base.Iterators.product(map(values, _sectors(T))...)) +end +function Base.size(::SectorValues{ProductSector{T}}) where {T<:SectorTuple} + return map(s -> length(values(s)), _sectors(T)) +end Base.length(P::SectorValues{<:ProductSector}) = *(size(P)...) function Base.iterate(::SectorValues{ProductSector{T}}, args...) where {T<:SectorTuple} @@ -24,22 +27,27 @@ end function Base.getindex(P::SectorValues{ProductSector{T}}, i::Int) where {T<:SectorTuple} Base.IteratorSize(P) isa IsInfinite && throw(ArgumentError("cannot index into infinite product sector")) - ProductSector{T}(getindex.(values.(_sectors(T)), Tuple(CartesianIndices(size(P))[i]))) + return ProductSector{T}(getindex.(values.(_sectors(T)), + Tuple(CartesianIndices(size(P))[i]))) end -function findindex(P::SectorValues{ProductSector{T}}, c::ProductSector{T}) where - {T<:SectorTuple} +function findindex(P::SectorValues{ProductSector{T}}, + c::ProductSector{T}) where + {T<:SectorTuple} Base.IteratorSize(P) isa IsInfinite && throw(ArgumentError("cannot index into infinite product sector")) - LinearIndices(size(P))[CartesianIndex(findindex.(values.(_sectors(T)), c.sectors))] + return LinearIndices(size(P))[CartesianIndex(findindex.(values.(_sectors(T)), + c.sectors))] end ProductSector{T}(args...) where {T<:SectorTuple} = ProductSector{T}(args) -Base.convert(::Type{ProductSector{T}}, t::Tuple) where {T<:SectorTuple} = - ProductSector{T}(convert(T, t)) +function Base.convert(::Type{ProductSector{T}}, t::Tuple) where {T<:SectorTuple} + return ProductSector{T}(convert(T, t)) +end -Base.one(::Type{ProductSector{T}}) where {I<:Sector, T<:Tuple{I}} = ProductSector((one(I),)) -Base.one(::Type{ProductSector{T}}) where {I<:Sector, T<:Tuple{I, Vararg{Sector}}} = - one(I) ⊠ one(ProductSector{Base.tuple_type_tail(T)}) +Base.one(::Type{ProductSector{T}}) where {I<:Sector,T<:Tuple{I}} = ProductSector((one(I),)) +function Base.one(::Type{ProductSector{T}}) where {I<:Sector,T<:Tuple{I,Vararg{Sector}}} + return one(I) ⊠ one(ProductSector{Base.tuple_type_tail(T)}) +end Base.conj(p::ProductSector) = ProductSector(map(conj, p.sectors)) function ⊗(p1::P, p2::P) where {P<:ProductSector} @@ -50,8 +58,9 @@ function ⊗(p1::P, p2::P) where {P<:ProductSector} end end -Nsymbol(a::P, b::P, c::P) where {P<:ProductSector} = - prod(map(Nsymbol, a.sectors, b.sectors, c.sectors)) +function Nsymbol(a::P, b::P, c::P) where {P<:ProductSector} + return prod(map(Nsymbol, a.sectors, b.sectors, c.sectors)) +end _firstsector(x::ProductSector) = x.sectors[1] _tailsector(x::ProductSector) = ProductSector(tail(x.sectors)) @@ -67,8 +76,10 @@ function Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P<:ProductSector} _kron(f₁, f₂) end end -Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P<:ProductSector{<:Tuple{Sector}}} = - Fsymbol(map(_firstsector, (a, b, c, d, e, f))...) +function Fsymbol(a::P, b::P, c::P, d::P, e::P, + f::P) where {P<:ProductSector{<:Tuple{Sector}}} + return Fsymbol(map(_firstsector, (a, b, c, d, e, f))...) +end function Rsymbol(a::P, b::P, c::P) where {P<:ProductSector} heads = map(_firstsector, (a, b, c)) @@ -81,8 +92,9 @@ function Rsymbol(a::P, b::P, c::P) where {P<:ProductSector} _kron(r1, r2) end end -Rsymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} = - Rsymbol(map(_firstsector, (a, b, c))...) +function Rsymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} + return Rsymbol(map(_firstsector, (a, b, c))...) +end function Bsymbol(a::P, b::P, c::P) where {P<:ProductSector} heads = map(_firstsector, (a, b, c)) @@ -95,8 +107,9 @@ function Bsymbol(a::P, b::P, c::P) where {P<:ProductSector} _kron(b1, b2) end end -Bsymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} = - Bsymbol(map(_firstsector, (a, b, c))...) +function Bsymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} + return Bsymbol(map(_firstsector, (a, b, c))...) +end function Asymbol(a::P, b::P, c::P) where {P<:ProductSector} heads = map(_firstsector, (a, b, c)) @@ -109,27 +122,32 @@ function Asymbol(a::P, b::P, c::P) where {P<:ProductSector} _kron(a1, a2) end end -Asymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} = - Asymbol(map(_firstsector, (a, b, c))...) +function Asymbol(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} + return Asymbol(map(_firstsector, (a, b, c))...) +end frobeniusschur(p::ProductSector) = prod(map(frobeniusschur, p.sectors)) function fusiontensor(a::P, b::P, c::P) where {P<:ProductSector} - return _kron(fusiontensor(map(_firstsector, (a,b,c))...), - fusiontensor(map(_tailsector, (a,b,c))...)) + return _kron(fusiontensor(map(_firstsector, (a, b, c))...), + fusiontensor(map(_tailsector, (a, b, c))...)) end -fusiontensor(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} = - fusiontensor(map(_firstsector, (a,b,c))...) +function fusiontensor(a::P, b::P, c::P) where {P<:ProductSector{<:Tuple{Sector}}} + return fusiontensor(map(_firstsector, (a, b, c))...) +end -FusionStyle(::Type{<:ProductSector{T}}) where {T<:SectorTuple} = - Base.:&(map(FusionStyle, _sectors(T))...) -BraidingStyle(::Type{<:ProductSector{T}}) where {T<:SectorTuple} = - Base.:&(map(BraidingStyle, _sectors(T))...) +function FusionStyle(::Type{<:ProductSector{T}}) where {T<:SectorTuple} + return Base.:&(map(FusionStyle, _sectors(T))...) +end +function BraidingStyle(::Type{<:ProductSector{T}}) where {T<:SectorTuple} + return Base.:&(map(BraidingStyle, _sectors(T))...) +end Base.isreal(::Type{<:ProductSector{T}}) where {T<:SectorTuple} = _isreal(T) _isreal(::Type{Tuple{}}) = true -_isreal(T::Type{<:SectorTuple}) = - isreal(Base.tuple_type_head(T)) && _isreal(Base.tuple_type_tail(T)) +function _isreal(T::Type{<:SectorTuple}) + return isreal(Base.tuple_type_head(T)) && _isreal(Base.tuple_type_tail(T)) +end fermionparity(P::ProductSector) = mapreduce(fermionparity, xor, P.sectors) @@ -137,8 +155,9 @@ dim(p::ProductSector) = *(dim.(p.sectors)...) Base.isequal(p1::ProductSector, p2::ProductSector) = isequal(p1.sectors, p2.sectors) Base.hash(p::ProductSector, h::UInt) = hash(p.sectors, h) -Base.isless(p1::ProductSector{T}, p2::ProductSector{T}) where {T} = - isless(reverse(p1.sectors), reverse(p2.sectors)) +function Base.isless(p1::ProductSector{T}, p2::ProductSector{T}) where {T} + return isless(reverse(p1.sectors), reverse(p2.sectors)) +end # Default construction from tensor product of sectors #----------------------------------------------------- @@ -164,41 +183,43 @@ group representations, we have `Irrep[G₁] ⊠ Irrep[G₂] == Irrep[G₁ × G ⊠(p1::ProductSector, s2::Sector) = ProductSector(tuple(p1.sectors..., s2)) ⊠(s1::Trivial, p2::ProductSector) = p2 ⊠(s1::Sector, p2::ProductSector) = ProductSector(tuple(s1, p2.sectors...)) -⊠(p1::ProductSector, p2::ProductSector) = - ProductSector(tuple(p1.sectors..., p2.sectors...)) +⊠(p1::ProductSector, p2::ProductSector) = ProductSector(tuple(p1.sectors..., p2.sectors...)) # grow types from the left using Base.tuple_type_cons ⊠(I1::Type{Trivial}, I2::Type{Trivial}) = Trivial ⊠(I1::Type{Trivial}, I2::Type{<:Sector}) = I2 ⊠(I1::Type{<:Sector}, I2::Type{<:Trivial}) = I1 -⊠(I1::Type{<:Sector}, I2::Type{<:Sector}) = ProductSector{Tuple{I1, I2}} +⊠(I1::Type{<:Sector}, I2::Type{<:Sector}) = ProductSector{Tuple{I1,I2}} ⊠(I1::Type{<:ProductSector}, I2::Type{Trivial}) = I1 ⊠(I1::Type{<:ProductSector}, I2::Type{<:Sector}) = I1 ⊠ ProductSector{Tuple{I2}} ⊠(::Type{Trivial}, P::Type{ProductSector{T}}) where {T<:SectorTuple} = P -⊠(I::Type{<:Sector}, ::Type{ProductSector{T}}) where {T<:SectorTuple} = - ProductSector{Base.tuple_type_cons(I, T)} +function ⊠(I::Type{<:Sector}, ::Type{ProductSector{T}}) where {T<:SectorTuple} + return ProductSector{Base.tuple_type_cons(I, T)} +end -⊠(::Type{ProductSector{Tuple{I}}}, - ::Type{ProductSector{T}}) where {I<:Sector, T<:SectorTuple} = - ProductSector{Base.tuple_type_cons(I, T)} +function ⊠(::Type{ProductSector{Tuple{I}}}, + ::Type{ProductSector{T}}) where {I<:Sector,T<:SectorTuple} + return ProductSector{Base.tuple_type_cons(I, T)} +end -⊠(::Type{ProductSector{T1}}, - I2::Type{ProductSector{T2}}) where {T1<:SectorTuple, T2<:SectorTuple} = - Base.tuple_type_head(T1) ⊠ (ProductSector{Base.tuple_type_tail(T1)} ⊠ I2) +function ⊠(::Type{ProductSector{T1}}, + I2::Type{ProductSector{T2}}) where {T1<:SectorTuple,T2<:SectorTuple} + return Base.tuple_type_head(T1) ⊠ (ProductSector{Base.tuple_type_tail(T1)} ⊠ I2) +end function Base.show(io::IO, P::ProductSector) sectors = P.sectors compact = get(io, :typeinfo, nothing) === typeof(P) sep = compact ? ", " : " ⊠ " print(io, "(") - for i = 1:length(sectors) + for i in 1:length(sectors) i == 1 || print(io, sep) io2 = compact ? IOContext(io, :typeinfo => typeof(sectors[i])) : io print(io2, sectors[i]) end - print(io, ")") + return print(io, ")") end function type_repr(P::Type{<:ProductSector}) @@ -235,8 +256,8 @@ end function Base.getindex(::IrrepTable, ::Type{ProductGroup{Gs}}) where {Gs<:GroupTuple} G1 = tuple_type_head(Gs) Grem = tuple_type_tail(Gs) - ProductSector{Tuple{Irrep[G1]}} ⊠ Irrep[ProductGroup{tuple_type_tail(Gs)}] + return ProductSector{Tuple{Irrep[G1]}} ⊠ Irrep[ProductGroup{tuple_type_tail(Gs)}] end function Base.getindex(::IrrepTable, ::Type{ProductGroup{Tuple{G}}}) where {G<:Group} - ProductSector{Tuple{Irrep[G]}} + return ProductSector{Tuple{Irrep[G]}} end diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index 10ddda9f..86e145a4 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -8,10 +8,10 @@ vector space that is implicitly assumed in most of matrix algebra. struct CartesianSpace <: ElementarySpace{ℝ} d::Int end -CartesianSpace(d::Integer = 0; dual = false) = CartesianSpace(Int(d)) -function CartesianSpace(dim::Pair; dual = false) +CartesianSpace(d::Integer=0; dual=false) = CartesianSpace(Int(d)) +function CartesianSpace(dim::Pair; dual=false) if dim.first === Trivial() - return CartesianSpace(dim.second; dual = dual) + return CartesianSpace(dim.second; dual=dual) else msg = "$(dim) is not a valid dimension for CartesianSpace" throw(SectorMismatch(msg)) @@ -42,8 +42,8 @@ dim(V::CartesianSpace) = V.d Base.axes(V::CartesianSpace) = Base.OneTo(dim(V)) Base.oneunit(::Type{CartesianSpace}) = CartesianSpace(1) -⊕(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d+V₂.d) -fuse(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d*V₂.d) +⊕(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d + V₂.d) +fuse(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d * V₂.d) flip(V::CartesianSpace) = V infimum(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(min(V₁.d, V₂.d)) diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index f0784fcb..01a77df7 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -9,10 +9,10 @@ struct ComplexSpace <: ElementarySpace{ℂ} d::Int dual::Bool end -ComplexSpace(d::Integer = 0; dual = false) = ComplexSpace(Int(d), dual) -function ComplexSpace(dim::Pair; dual = false) +ComplexSpace(d::Integer=0; dual=false) = ComplexSpace(Int(d), dual) +function ComplexSpace(dim::Pair; dual=false) if dim.first === Trivial() - return ComplexSpace(dim.second; dual = dual) + return ComplexSpace(dim.second; dual=dual) else msg = "$(dim) is not a valid dimension for ComplexSpace" throw(SectorMismatch(msg)) diff --git a/src/spaces/deligne.jl b/src/spaces/deligne.jl index 2a8ea638..f846895a 100644 --- a/src/spaces/deligne.jl +++ b/src/spaces/deligne.jl @@ -18,23 +18,25 @@ natural representation spaces of the direct product of two groups. function ⊠(V::GradedSpace, P₀::ProductSpace{<:ElementarySpace{ℂ},0}) I₁ = sectortype(V) I₂ = sectortype(P₀) - return Vect[I₁ ⊠ I₂](ifelse(isdual(V), dual(c), c) ⊠ one(I₂) => dim(V, c) for c in sectors(V); dual = isdual(V)) + return Vect[I₁ ⊠ I₂](ifelse(isdual(V), dual(c), c) ⊠ one(I₂) => dim(V, c) + for c in sectors(V); dual=isdual(V)) end function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, V::GradedSpace) I₁ = sectortype(P₀) I₂ = sectortype(V) - return Vect[I₁ ⊠ I₂](one(I₁) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) for c in sectors(V); dual = isdual(V)) + return Vect[I₁ ⊠ I₂](one(I₁) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) + for c in sectors(V); dual=isdual(V)) end function ⊠(V::ComplexSpace, P₀::ProductSpace{<:ElementarySpace{ℂ},0}) I₂ = sectortype(P₀) - return Vect[I₂](one(I₂) => dim(V); dual = isdual(V)) + return Vect[I₂](one(I₂) => dim(V); dual=isdual(V)) end function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, V::ComplexSpace) I₁ = sectortype(P₀) - return Vect[I₁](one(I₁) => dim(V); dual = isdual(V)) + return Vect[I₁](one(I₁) => dim(V); dual=isdual(V)) end function ⊠(P::ProductSpace{<:ElementarySpace{ℂ},0}, @@ -49,7 +51,7 @@ function ⊠(P::ProductSpace{<:ElementarySpace{ℂ}}, P₀::ProductSpace{<:Eleme I₂ = sectortype(P₀) S = Vect[I₁ ⊠ I₂] N = length(P) - return ProductSpace{S,N}(map(V->V ⊠ P₀, tuple(P...))) + return ProductSpace{S,N}(map(V -> V ⊠ P₀, tuple(P...))) end function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, P::ProductSpace{<:ElementarySpace{ℂ}}) @@ -57,5 +59,5 @@ function ⊠(P₀::ProductSpace{<:ElementarySpace{ℂ},0}, P::ProductSpace{<:Ele I₂ = sectortype(P) S = Vect[I₁ ⊠ I₂] N = length(P) - return ProductSpace{S,N}(map(V->P₀ ⊠ V, tuple(P...))) + return ProductSpace{S,N}(map(V -> P₀ ⊠ V, tuple(P...))) end diff --git a/src/spaces/generalspace.jl b/src/spaces/generalspace.jl index 0f3268fd..3f725683 100644 --- a/src/spaces/generalspace.jl +++ b/src/spaces/generalspace.jl @@ -19,8 +19,9 @@ struct GeneralSpace{𝕜} <: ElementarySpace{𝕜} end end end -GeneralSpace{𝕜}(d::Int = 0; dual::Bool = false, conj::Bool = false) where {𝕜} = - GeneralSpace{𝕜}(d, dual, conj) +function GeneralSpace{𝕜}(d::Int=0; dual::Bool=false, conj::Bool=false) where {𝕜} + return GeneralSpace{𝕜}(d, dual, conj) +end dim(V::GeneralSpace) = V.d isdual(V::GeneralSpace) = V.dual diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 7146b64f..214ffbea 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -30,49 +30,55 @@ struct GradedSpace{I<:Sector,D} <: ElementarySpace{ℂ} end sectortype(::Type{<:GradedSpace{I}}) where {I<:Sector} = I -function GradedSpace{I, NTuple{N, Int}}(dims; dual::Bool = false) where {I, N} - d = ntuple(n->0, N) - isset = ntuple(n->false, N) +function GradedSpace{I,NTuple{N,Int}}(dims; dual::Bool=false) where {I,N} + d = ntuple(n -> 0, N) + isset = ntuple(n -> false, N) for (c, dc) in dims i = findindex(values(I), convert(I, c)) isset[i] && throw(ArgumentError("Sector $c appears multiple times")) isset = TupleTools.setindex(isset, true, i) d = TupleTools.setindex(d, dc, i) end - return GradedSpace{I, NTuple{N, Int}}(d, dual) + return GradedSpace{I,NTuple{N,Int}}(d, dual) +end +function GradedSpace{I,NTuple{N,Int}}(dims::Pair; dual::Bool=false) where {I,N} + return GradedSpace{I,NTuple{N,Int}}((dims,); dual=dual) end -GradedSpace{I, NTuple{N, Int}}(dims::Pair; dual::Bool = false) where {I, N} = - GradedSpace{I, NTuple{N, Int}}((dims,); dual = dual) -function GradedSpace{I, SectorDict{I, Int}}(dims; dual::Bool = false) where {I<:Sector} - d = SectorDict{I, Int}() +function GradedSpace{I,SectorDict{I,Int}}(dims; dual::Bool=false) where {I<:Sector} + d = SectorDict{I,Int}() for (c, dc) in dims k = convert(I, c) haskey(d, k) && throw(ArgumentError("Sector $k appears multiple times")) - !iszero(dc) && push!(d, k=>dc) + !iszero(dc) && push!(d, k => dc) end - return GradedSpace{I, SectorDict{I, Int}}(d, dual) + return GradedSpace{I,SectorDict{I,Int}}(d, dual) +end +function GradedSpace{I,SectorDict{I,Int}}(dims::Pair; dual::Bool=false) where {I<:Sector} + return GradedSpace{I,SectorDict{I,Int}}((dims,); dual=dual) end -GradedSpace{I, SectorDict{I, Int}}(dims::Pair; dual::Bool = false) where {I<:Sector} = - GradedSpace{I, SectorDict{I, Int}}((dims,); dual = dual) GradedSpace{I,D}(; kwargs...) where {I<:Sector,D} = GradedSpace{I,D}((); kwargs...) -GradedSpace{I,D}(d1::Pair, d2::Pair, dims::Vararg{Pair}; kwargs...) where {I<:Sector,D} = - GradedSpace{I,D}((d1, d2, dims...); kwargs...) +function GradedSpace{I,D}(d1::Pair, d2::Pair, dims::Vararg{Pair}; + kwargs...) where {I<:Sector,D} + return GradedSpace{I,D}((d1, d2, dims...); kwargs...) +end GradedSpace{I}(args...; kwargs...) where {I<:Sector} = Vect[I](args..., kwargs...) -GradedSpace(dims::Tuple{Vararg{Pair{I, <:Integer}}}; dual::Bool = false) where {I<:Sector} = - Vect[I](dims; dual = dual) -GradedSpace(dims::Vararg{Pair{I, <:Integer}}; dual::Bool = false) where {I<:Sector} = - Vect[I](dims; dual = dual) -GradedSpace(dims::AbstractDict{I, <:Integer}; dual::Bool = false) where {I<:Sector} = - Vect[I](dims; dual = dual) +function GradedSpace(dims::Tuple{Vararg{Pair{I,<:Integer}}}; + dual::Bool=false) where {I<:Sector} + return Vect[I](dims; dual=dual) +end +function GradedSpace(dims::Vararg{Pair{I,<:Integer}}; dual::Bool=false) where {I<:Sector} + return Vect[I](dims; dual=dual) +end +function GradedSpace(dims::AbstractDict{I,<:Integer}; dual::Bool=false) where {I<:Sector} + return Vect[I](dims; dual=dual) +end # not inferrable -GradedSpace(g::Base.Generator; dual::Bool = false) = - GradedSpace(g...; dual = dual) -GradedSpace(g::AbstractDict; dual::Bool = false) = - GradedSpace(g...; dual = dual) +GradedSpace(g::Base.Generator; dual::Bool=false) = GradedSpace(g...; dual=dual) +GradedSpace(g::AbstractDict; dual::Bool=false) = GradedSpace(g...; dual=dual) Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) @@ -105,8 +111,9 @@ Base.conj(V::GradedSpace) = typeof(V)(V.dims, !V.dual) isdual(V::GradedSpace) = V.dual # equality / comparison -Base.:(==)(V₁::GradedSpace, V₂::GradedSpace) = - sectortype(V₁) == sectortype(V₂) && (V₁.dims == V₂.dims) && V₁.dual == V₂.dual +function Base.:(==)(V₁::GradedSpace, V₂::GradedSpace) + return sectortype(V₁) == sectortype(V₂) && (V₁.dims == V₂.dims) && V₁.dual == V₂.dual +end # axes Base.axes(V::GradedSpace) = Base.OneTo(dim(V)) @@ -114,12 +121,12 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} offset = 0 for c′ in sectors(V) c′ == c && break - offset += dim(c′)*dim(V, c′) + offset += dim(c′) * dim(V, c′) end - return (offset+1):(offset+dim(c)*dim(V, c)) + return (offset + 1):(offset + dim(c) * dim(V, c)) end -Base.oneunit(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I)=>1) +Base.oneunit(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 1) # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so @@ -128,27 +135,27 @@ function ⊕(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} dual1 = isdual(V₁) dual1 == isdual(V₂) || throw(SpaceMismatch("Direct sum of a vector space and a dual space does not exist")) - dims = SectorDict{I, Int}() + dims = SectorDict{I,Int}() for c in union(sectors(V₁), sectors(V₂)) cout = ifelse(dual1, dual(c), c) dims[cout] = dim(V₁, c) + dim(V₂, c) end - return typeof(V₁)(dims; dual = dual1) + return typeof(V₁)(dims; dual=dual1) end function flip(V::GradedSpace{I}) where {I<:Sector} if isdual(V) - typeof(V)(c=>dim(V, c) for c in sectors(V)) + typeof(V)(c => dim(V, c) for c in sectors(V)) else - typeof(V)(dual(c)=>dim(V, c) for c in sectors(V))' + typeof(V)(dual(c) => dim(V, c) for c in sectors(V))' end end function fuse(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} - dims = SectorDict{I, Int}() + dims = SectorDict{I,Int}() for a in sectors(V₁), b in sectors(V₂) for c in a ⊗ b - dims[c] = get(dims, c, 0) + Nsymbol(a, b, c)*dim(V₁, a)*dim(V₂, b) + dims[c] = get(dims, c, 0) + Nsymbol(a, b, c) * dim(V₁, a) * dim(V₂, b) end end return typeof(V₁)(dims) @@ -156,8 +163,9 @@ end function infimum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} if V₁.dual == V₂.dual - typeof(V₁)(c=>min(dim(V₁, c), dim(V₂, c)) for c in - union(sectors(V₁), sectors(V₂)), dual = V₁.dual) + typeof(V₁)(c => min(dim(V₁, c), dim(V₂, c)) + for c in + union(sectors(V₁), sectors(V₂)), dual in V₁.dual) else throw(SpaceMismatch("Infimum of space and dual space does not exist")) end @@ -165,8 +173,9 @@ end function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I<:Sector} if V₁.dual == V₂.dual - typeof(V₁)(c=>max(dim(V₁, c), dim(V₂, c)) for c in - union(sectors(V₁), sectors(V₂)), dual = V₁.dual) + typeof(V₁)(c => max(dim(V₁, c), dim(V₂, c)) + for c in + union(sectors(V₁), sectors(V₂)), dual in V₁.dual) else throw(SpaceMismatch("Supremum of space and dual space does not exist")) end @@ -202,11 +211,11 @@ const Vect = SpaceTable() Base.getindex(::SpaceTable) = ComplexSpace Base.getindex(::SpaceTable, ::Type{Trivial}) = ComplexSpace function Base.getindex(::SpaceTable, I::Type{<:Sector}) - if Base.IteratorSize(values(I)) isa Union{HasLength, HasShape} + if Base.IteratorSize(values(I)) isa Union{HasLength,HasShape} N = length(values(I)) - return GradedSpace{I, NTuple{N, Int}} + return GradedSpace{I,NTuple{N,Int}} else - return GradedSpace{I, SectorDict{I, Int}} + return GradedSpace{I,SectorDict{I,Int}} end end @@ -224,12 +233,12 @@ See also [`Irrep`](@ref) and [`Vect`](@ref). const Rep = RepTable() Base.getindex(::RepTable, G::Type{<:Group}) = Vect[Irrep[G]] -type_repr(::Type{<:GradedSpace{I}}) where {I<:Sector} = - "Vect[" * type_repr(I) * "]" -type_repr(::Type{<:GradedSpace{<:AbstractIrrep{G}}}) where {G<:Group} = - "Rep[" * type_repr(G) * "]" +type_repr(::Type{<:GradedSpace{I}}) where {I<:Sector} = "Vect[" * type_repr(I) * "]" +function type_repr(::Type{<:GradedSpace{<:AbstractIrrep{G}}}) where {G<:Group} + return "Rep[" * type_repr(G) * "]" +end function type_repr(::Type{<:GradedSpace{ProductSector{T}}}) where - {T<:Tuple{Vararg{AbstractIrrep}}} + {T<:Tuple{Vararg{AbstractIrrep}}} sectors = T.parameters s = "Rep[" for i in 1:length(sectors) @@ -243,15 +252,15 @@ function type_repr(::Type{<:GradedSpace{ProductSector{T}}}) where end # Specific constructors for Z_N -const ZNSpace{N} = GradedSpace{ZNIrrep{N}, NTuple{N,Int}} -ZNSpace{N}(dims::NTuple{N, Int}; dual::Bool = false) where {N} = ZNSpace{N}(dims, dual) -ZNSpace{N}(dims::Vararg{Int, N}; dual::Bool = false) where {N} = ZNSpace{N}(dims, dual) -ZNSpace(dims::NTuple{N, Int}; dual::Bool = false) where {N} = ZNSpace{N}(dims, dual) -ZNSpace(dims::Vararg{Int, N}; dual::Bool = false) where {N} = ZNSpace{N}(dims, dual) +const ZNSpace{N} = GradedSpace{ZNIrrep{N},NTuple{N,Int}} +ZNSpace{N}(dims::NTuple{N,Int}; dual::Bool=false) where {N} = ZNSpace{N}(dims, dual) +ZNSpace{N}(dims::Vararg{Int,N}; dual::Bool=false) where {N} = ZNSpace{N}(dims, dual) +ZNSpace(dims::NTuple{N,Int}; dual::Bool=false) where {N} = ZNSpace{N}(dims, dual) +ZNSpace(dims::Vararg{Int,N}; dual::Bool=false) where {N} = ZNSpace{N}(dims, dual) # TODO: Do we still need all of those # ASCII type aliases -const ZNSpace{N} = GradedSpace{ZNIrrep{N}, NTuple{N,Int}} +const ZNSpace{N} = GradedSpace{ZNIrrep{N},NTuple{N,Int}} const Z2Space = ZNSpace{2} const Z3Space = ZNSpace{3} const Z4Space = ZNSpace{4} diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 2368ddc6..3a036f59 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -8,7 +8,7 @@ Represents the linear space of morphisms with codomain of type `P1` and domain o 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. """ -struct HomSpace{S<:ElementarySpace, P1<:CompositeSpace{S}, P2<:CompositeSpace{S}} +struct HomSpace{S<:ElementarySpace,P1<:CompositeSpace{S},P2<:CompositeSpace{S}} codomain::P1 domain::P2 end @@ -23,29 +23,33 @@ function Base.adjoint(W::HomSpace{S}) where {S} end Base.hash(W::HomSpace, h::UInt) = hash(domain(W), hash(codomain(W), h)) -Base.:(==)(W₁::HomSpace, W₂::HomSpace) = - (W₁.codomain == W₂.codomain) && (W₁.domain == W₂.domain) +function Base.:(==)(W₁::HomSpace, W₂::HomSpace) + return (W₁.codomain == W₂.codomain) && (W₁.domain == W₂.domain) +end spacetype(W::HomSpace) = spacetype(typeof(W)) sectortype(W::HomSpace) = sectortype(typeof(W)) field(W::HomSpace) = field(typeof(W)) -spacetype(::Type{<:HomSpace{S}}) where S = S +spacetype(::Type{<:HomSpace{S}}) where {S} = S field(L::Type{<:HomSpace}) = field(spacetype(L)) sectortype(L::Type{<:HomSpace}) = sectortype(spacetype(L)) -const TensorSpace{S<:ElementarySpace} = Union{S, ProductSpace{S}} -const TensorMapSpace{S<:ElementarySpace, N₁, N₂} = - HomSpace{S, ProductSpace{S, N₁}, ProductSpace{S, N₂}} +const TensorSpace{S<:ElementarySpace} = Union{S,ProductSpace{S}} +const TensorMapSpace{S<:ElementarySpace,N₁,N₂} = HomSpace{S,ProductSpace{S,N₁}, + ProductSpace{S,N₂}} -Base.getindex(W::TensorMapSpace{<:IndexSpace, N₁, N₂}, i) where {N₁, N₂} = - i <= N₁ ? codomain(W)[i] : dual(domain(W)[i-N₁]) +function Base.getindex(W::TensorMapSpace{<:IndexSpace,N₁,N₂}, i) where {N₁,N₂} + return i <= N₁ ? codomain(W)[i] : dual(domain(W)[i - N₁]) +end -→(dom::TensorSpace{S}, codom::TensorSpace{S}) where {S<:ElementarySpace} = - HomSpace(ProductSpace(codom), ProductSpace(dom)) +function →(dom::TensorSpace{S}, codom::TensorSpace{S}) where {S<:ElementarySpace} + return HomSpace(ProductSpace(codom), ProductSpace(dom)) +end -←(codom::TensorSpace{S}, dom::TensorSpace{S}) where {S<:ElementarySpace} = - HomSpace(ProductSpace(codom), ProductSpace(dom)) +function ←(codom::TensorSpace{S}, dom::TensorSpace{S}) where {S<:ElementarySpace} + return HomSpace(ProductSpace(codom), ProductSpace(dom)) +end function Base.show(io::IO, W::HomSpace) if length(W.codomain) == 1 @@ -82,9 +86,9 @@ function blocksectors(W::HomSpace) if N₁ == 0 || N₂ == 0 return (one(I),) elseif N₂ <= N₁ - return filter!(c->hasblock(codom, c), collect(blocksectors(dom))) + return filter!(c -> hasblock(codom, c), collect(blocksectors(dom))) else - return filter!(c->hasblock(dom, c), collect(blocksectors(codom))) + return filter!(c -> hasblock(dom, c), collect(blocksectors(codom))) end end @@ -95,7 +99,7 @@ Query whether a coupled sector `c` appears in both the codomain and domain of `W See also [`blocksectors`](@ref). """ -hasblock(W::HomSpace, c::Sector) = hasblock(codomain(W), c) && hasblock(domain(W), c) +hasblock(W::HomSpace, c::Sector) = hasblock(codomain(W), c) && hasblock(domain(W), c) """ dim(W::HomSpace) diff --git a/src/spaces/planarspace.jl b/src/spaces/planarspace.jl index e5122124..76411006 100644 --- a/src/spaces/planarspace.jl +++ b/src/spaces/planarspace.jl @@ -8,4 +8,4 @@ Base.:^(::PlanarNumbers, d::Int) = Vect[PlanarTrivial](PlanarTrivial() => d) # convenience show function Base.show(io::IO, V::GradedSpace{PlanarTrivial}) return print(io, isdual(V) ? "(ℙ^$(dim(V)))'" : "ℙ^$(dim(V))") -end \ No newline at end of file +end diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index bdf80c50..bc5094fd 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -5,14 +5,14 @@ A `ProductSpace` is a tensor product space of `N` vector spaces of type `S<:ElementarySpace`. Only tensor products between [`ElementarySpace`](@ref) objects of the same type are allowed. """ -struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S} - spaces::NTuple{N, S} -end -ProductSpace(spaces::Vararg{S, N}) where {S<:ElementarySpace, N} = - ProductSpace{S, N}(spaces) -ProductSpace{S, N}(spaces::Vararg{S, N}) where {S<:ElementarySpace, N} = - ProductSpace{S, N}(spaces) -ProductSpace{S}(spaces) where {S<:ElementarySpace} = ProductSpace{S, length(spaces)}(spaces) +struct ProductSpace{S<:ElementarySpace,N} <: CompositeSpace{S} + spaces::NTuple{N,S} +end +ProductSpace(spaces::Vararg{S,N}) where {S<:ElementarySpace,N} = ProductSpace{S,N}(spaces) +function ProductSpace{S,N}(spaces::Vararg{S,N}) where {S<:ElementarySpace,N} + return ProductSpace{S,N}(spaces) +end +ProductSpace{S}(spaces) where {S<:ElementarySpace} = ProductSpace{S,length(spaces)}(spaces) ProductSpace(P::ProductSpace) = P # Corresponding methods @@ -29,7 +29,7 @@ dim(P::ProductSpace) = prod(dims(P)) Base.axes(P::ProductSpace) = map(axes, P.spaces) Base.axes(P::ProductSpace, n::Int) = axes(P.spaces[n]) -dual(P::ProductSpace{<:ElementarySpace, 0}) = P +dual(P::ProductSpace{<:ElementarySpace,0}) = P dual(P::ProductSpace) = ProductSpace(map(dual, reverse(P.spaces))) # Base.conj(P::ProductSpace) = ProductSpace(map(conj, P.spaces)) @@ -44,10 +44,10 @@ function Base.show(io::IO, P::ProductSpace{S}) where {S<:ElementarySpace} end print(io, "(") for i in 1:length(spaces) - i==1 || print(io, " ⊗ ") + i == 1 || print(io, " ⊗ ") show(io, spaces[i]) end - print(io, ")") + return print(io, ")") end # more specific methods @@ -58,10 +58,12 @@ Return an iterator over all possible combinations of sectors (represented as an `NTuple{N, sectortype(S)}`) that can appear within the tensor product space `P`. """ sectors(P::ProductSpace) = _sectors(P, sectortype(P)) -_sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{Trivial}) where {N} = - OneOrNoneIterator(dim(P) != 0, ntuple(n->Trivial(), N)) -_sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{<:Sector}) where {N} = - product(map(sectors, P.spaces)...) +function _sectors(P::ProductSpace{<:ElementarySpace,N}, ::Type{Trivial}) where {N} + return OneOrNoneIterator(dim(P) != 0, ntuple(n -> Trivial(), N)) +end +function _sectors(P::ProductSpace{<:ElementarySpace,N}, ::Type{<:Sector}) where {N} + return product(map(sectors, P.spaces)...) +end """ hassector(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace} @@ -70,8 +72,9 @@ _sectors(P::ProductSpace{<:ElementarySpace, N}, ::Type{<:Sector}) where {N} = Query whether `P` has a non-zero degeneracy of sector `s`, representing a combination of sectors on the individual tensor indices. """ -hassector(V::ProductSpace{<:ElementarySpace, N}, s::NTuple{N}) where {N} = - reduce(&, map(hassector, V.spaces, s); init = true) +function hassector(V::ProductSpace{<:ElementarySpace,N}, s::NTuple{N}) where {N} + return reduce(&, map(hassector, V.spaces, s); init=true) +end """ dims(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace} @@ -80,8 +83,9 @@ hassector(V::ProductSpace{<:ElementarySpace, N}, s::NTuple{N}) where {N} = Return the degeneracy dimensions corresponding to a tuple of sectors `s` for each of the spaces in the tensor product `P`. """ -dims(P::ProductSpace{<:ElementarySpace, N}, sector::NTuple{N, <:Sector}) where {N} = - map(dim, P.spaces, sector) +function dims(P::ProductSpace{<:ElementarySpace,N}, sector::NTuple{N,<:Sector}) where {N} + return map(dim, P.spaces, sector) +end """ dim(P::ProductSpace{S, N}, s::NTuple{N, sectortype(S)}) where {S<:ElementarySpace} @@ -90,11 +94,14 @@ dims(P::ProductSpace{<:ElementarySpace, N}, sector::NTuple{N, <:Sector}) where { 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))``. """ -dim(P::ProductSpace{<:ElementarySpace, N}, sector::NTuple{N, <:Sector}) where {N} = - reduce(*, dims(P, sector); init = 1) +function dim(P::ProductSpace{<:ElementarySpace,N}, sector::NTuple{N,<:Sector}) where {N} + return reduce(*, dims(P, sector); init=1) +end -Base.axes(P::ProductSpace{<:ElementarySpace, N}, sectors::NTuple{N, <:Sector}) where {N} = - map(axes, P.spaces, sectors) +function Base.axes(P::ProductSpace{<:ElementarySpace,N}, + sectors::NTuple{N,<:Sector}) where {N} + return map(axes, P.spaces, sectors) +end """ blocksectors(P::ProductSpace) @@ -103,7 +110,7 @@ Return an iterator over the different unique coupled sector labels, i.e. the dif fusion outputs that can be obtained by fusing the sectors present in the different spaces that make up the `ProductSpace` instance. """ -function blocksectors(P::ProductSpace{S, N}) where {S, N} +function blocksectors(P::ProductSpace{S,N}) where {S,N} I = sectortype(S) if I == Trivial return OneOrNoneIterator(dim(P) != 0, Trivial()) @@ -159,7 +166,7 @@ function blockdim(P::ProductSpace, c::Sector) d = 0 for s in sectors(P) ds = dim(P, s) - d += length(fusiontrees(s, c))*ds + d += length(fusiontrees(s, c)) * ds end return d end @@ -170,16 +177,19 @@ Base.hash(P::ProductSpace, h::UInt) = hash(P.spaces, h) # Default construction from product of spaces #--------------------------------------------- -⊗(V₁::S, V₂::S) where {S<:ElementarySpace}= ProductSpace((V₁, V₂)) -⊗(P1::ProductSpace{S}, V₂::S) where {S<:ElementarySpace} = - ProductSpace(tuple(P1.spaces..., V₂)) -⊗(V₁::S, P2::ProductSpace{S}) where {S<:ElementarySpace} = - ProductSpace(tuple(V₁, P2.spaces...)) -⊗(P1::ProductSpace{S}, P2::ProductSpace{S}) where {S<:ElementarySpace} = - ProductSpace(tuple(P1.spaces..., P2.spaces...)) -⊗(P::ProductSpace{S, 0}, ::ProductSpace{S, 0}) where {S<:ElementarySpace} = P -⊗(P::ProductSpace{S}, ::ProductSpace{S, 0}) where {S<:ElementarySpace} = P -⊗(::ProductSpace{S, 0}, P::ProductSpace{S}) where {S<:ElementarySpace} = P +⊗(V₁::S, V₂::S) where {S<:ElementarySpace} = ProductSpace((V₁, V₂)) +function ⊗(P1::ProductSpace{S}, V₂::S) where {S<:ElementarySpace} + return ProductSpace(tuple(P1.spaces..., V₂)) +end +function ⊗(V₁::S, P2::ProductSpace{S}) where {S<:ElementarySpace} + return ProductSpace(tuple(V₁, P2.spaces...)) +end +function ⊗(P1::ProductSpace{S}, P2::ProductSpace{S}) where {S<:ElementarySpace} + return ProductSpace(tuple(P1.spaces..., P2.spaces...)) +end +⊗(P::ProductSpace{S,0}, ::ProductSpace{S,0}) where {S<:ElementarySpace} = P +⊗(P::ProductSpace{S}, ::ProductSpace{S,0}) where {S<:ElementarySpace} = P +⊗(::ProductSpace{S,0}, P::ProductSpace{S}) where {S<:ElementarySpace} = P ⊗(V::ElementarySpace) = ProductSpace((V,)) ⊗(P::ProductSpace) = P @@ -192,17 +202,18 @@ Return a tensor product of zero spaces of type `S`, i.e. this is the unit object tensor product operation, such that `V ⊗ one(V) == V`. """ Base.one(V::VectorSpace) = one(typeof(V)) -Base.one(::Type{<:ProductSpace{S}}) where {S<:ElementarySpace} = ProductSpace{S, 0}(()) -Base.one(::Type{S}) where {S<:ElementarySpace} = ProductSpace{S, 0}(()) +Base.one(::Type{<:ProductSpace{S}}) where {S<:ElementarySpace} = ProductSpace{S,0}(()) +Base.one(::Type{S}) where {S<:ElementarySpace} = ProductSpace{S,0}(()) Base.convert(::Type{<:ProductSpace}, V::ElementarySpace) = ProductSpace((V,)) -Base.:^(V::ElementarySpace, N::Int) = ProductSpace{typeof(V), N}(ntuple(n->V, N)) -Base.:^(V::ProductSpace, N::Int) = ⊗(ntuple(n->V, N)...) -Base.literal_pow(::typeof(^), V::ElementarySpace, p::Val{N}) where N = - ProductSpace{typeof(V), N}(ntuple(n->V, p)) -Base.convert(::Type{S}, P::ProductSpace{S, 0}) where {S<:ElementarySpace} = oneunit(S) +Base.:^(V::ElementarySpace, N::Int) = ProductSpace{typeof(V),N}(ntuple(n -> V, N)) +Base.:^(V::ProductSpace, N::Int) = ⊗(ntuple(n -> V, N)...) +function Base.literal_pow(::typeof(^), V::ElementarySpace, p::Val{N}) where {N} + return ProductSpace{typeof(V),N}(ntuple(n -> V, p)) +end +Base.convert(::Type{S}, P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S) Base.convert(::Type{S}, P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) -fuse(P::ProductSpace{S, 0}) where {S<:ElementarySpace} = oneunit(S) +fuse(P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S) fuse(P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) """ @@ -214,7 +225,7 @@ underlying field of scalars, i.e. `oneunit(S)`. With the keyword arguments, one to insert the conjugated or dual space instead, which are all isomorphic to the field of scalars. """ -function insertunit(P::ProductSpace, i::Int = length(P)+1; dual = false, conj = false) +function insertunit(P::ProductSpace, i::Int=length(P) + 1; dual=false, conj=false) u = oneunit(spacetype(P)) if dual u = TensorKit.dual(u) @@ -222,7 +233,7 @@ function insertunit(P::ProductSpace, i::Int = length(P)+1; dual = false, conj = if conj u = TensorKit.conj(u) end - ProductSpace(TupleTools.insertafter(P.spaces, i-1, (u,))) + return ProductSpace(TupleTools.insertafter(P.spaces, i - 1, (u,))) end # Functionality for extracting and iterating over spaces @@ -230,11 +241,11 @@ end Base.length(P::ProductSpace) = length(P.spaces) Base.getindex(P::ProductSpace, n::Integer) = P.spaces[n] -@inline function Base.iterate(P::ProductSpace, ::Val{i} = Val(1)) where {i} +@inline function Base.iterate(P::ProductSpace, ::Val{i}=Val(1)) where {i} if i > length(P) return nothing else - return P.spaces[i], Val(i+1) + return P.spaces[i], Val(i + 1) end end Base.indexed_iterate(P::ProductSpace, args...) = Base.indexed_iterate(P.spaces, args...) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index e1c6e05a..b213a6c1 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -150,9 +150,10 @@ individual spaces `V₁`, `V₂`, ..., or the spaces contained in `P`. """ function fuse end fuse(V::ElementarySpace) = V -fuse(V₁::VectorSpace, V₂::VectorSpace, V₃::VectorSpace...) = - fuse(fuse(fuse(V₁), fuse(V₂)), V₃...) - # calling fuse on V₁ and V₂ will allow these to be `ProductSpace` +function fuse(V₁::VectorSpace, V₂::VectorSpace, V₃::VectorSpace...) + return fuse(fuse(fuse(V₁), fuse(V₂)), V₃...) +end +# calling fuse on V₁ and V₂ will allow these to be `ProductSpace` """ flip(V::S) where {S<:ElementarySpace} -> S @@ -218,8 +219,9 @@ Base.axes(V::ElementarySpace, ::Trivial) = axes(V) Return an iterator over the different sectors of `V`. """ sectors(V::ElementarySpace) = OneOrNoneIterator(dim(V) != 0, Trivial()) -dim(V::ElementarySpace, ::Trivial) = - sectortype(V) == Trivial ? dim(V) : throw(SectorMismatch()) +function dim(V::ElementarySpace, ::Trivial) + return sectortype(V) == Trivial ? dim(V) : throw(SectorMismatch()) +end # Composite vector spaces #------------------------- @@ -341,7 +343,7 @@ such that `V ≾ V₁`, `V ≾ V₂`, ... and no other `W ≻ V` has this proper that all arguments have the same value of `isdual( )`, and also the return value `V` will have the same value. """ -infimum(V₁::S, V₂::S, V₃::S...) where S<:ElementarySpace = infimum(infimum(V₁, V₂), V₃...) +infimum(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} = infimum(infimum(V₁, V₂), V₃...) """ supremum(V₁::ElementarySpace, V₂::ElementarySpace, V₃::ElementarySpace...) @@ -351,4 +353,6 @@ such that `V ≿ V₁`, `V ≿ V₂`, ... and no other `W ≺ V` has this proper that all arguments have the same value of `isdual( )`, and also the return value `V` will have the same value. """ -supremum(V₁::S, V₂::S, V₃::S...) where S<:ElementarySpace = supremum(supremum(V₁, V₂), V₃...) +function supremum(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} + return supremum(supremum(V₁, V₂), V₃...) +end diff --git a/src/tensors/adjoint.jl b/src/tensors/adjoint.jl index b1654542..43ad110d 100644 --- a/src/tensors/adjoint.jl +++ b/src/tensors/adjoint.jl @@ -6,13 +6,15 @@ Specific subtype of [`AbstractTensorMap`](@ref) that is a lazy wrapper for representing the adjoint of an instance of [`TensorMap`](@ref). """ -struct AdjointTensorMap{S<:IndexSpace, N₁, N₂, I<:Sector, A, F₁, F₂} <: - AbstractTensorMap{S, N₁, N₂} - parent::TensorMap{S, N₂, N₁, I, A, F₂, F₁} +struct AdjointTensorMap{S<:IndexSpace,N₁,N₂,I<:Sector,A,F₁,F₂} <: + AbstractTensorMap{S,N₁,N₂} + parent::TensorMap{S,N₂,N₁,I,A,F₂,F₁} end -const AdjointTrivialTensorMap{S<:IndexSpace, N₁, N₂, A<:DenseMatrix} = - AdjointTensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing} +#! format: off +const AdjointTrivialTensorMap{S<:IndexSpace,N₁,N₂,A<:DenseMatrix} = + AdjointTensorMap{S,N₁,N₂,Trivial,A,Nothing,Nothing} +#! format: on # Constructor: construct from taking adjoint of a tensor Base.adjoint(t::TensorMap) = AdjointTensorMap(t) @@ -26,8 +28,10 @@ domain(t::AdjointTensorMap) = codomain(t.parent) blocksectors(t::AdjointTensorMap) = blocksectors(t.parent) -storagetype(::Type{<:AdjointTensorMap{<:IndexSpace, N₁, N₂, Trivial, A}}) where {N₁, N₂, A<:DenseMatrix} = A -storagetype(::Type{<:AdjointTensorMap{<:IndexSpace, N₁, N₂, I, <:SectorDict{I, A}}}) where {N₁, N₂, I<:Sector, A<:DenseMatrix} = A +#! format: off +storagetype(::Type{<:AdjointTensorMap{<:IndexSpace,N₁,N₂,Trivial,A}}) where {N₁,N₂,A<:DenseMatrix} = A +storagetype(::Type{<:AdjointTensorMap{<:IndexSpace,N₁,N₂,I,<:SectorDict{I,A}}}) where {N₁, N₂,I<:Sector,A<:DenseMatrix} = A +#! format: on dim(t::AdjointTensorMap) = dim(t.parent) @@ -35,33 +39,37 @@ dim(t::AdjointTensorMap) = dim(t.parent) #---------- hasblock(t::AdjointTensorMap, s::Sector) = hasblock(t.parent, s) block(t::AdjointTensorMap, s::Sector) = block(t.parent, s)' -blocks(t::AdjointTensorMap) = (c=>b' for (c, b) in blocks(t.parent)) +blocks(t::AdjointTensorMap) = (c => b' for (c, b) in blocks(t.parent)) fusiontrees(::AdjointTrivialTensorMap) = ((nothing, nothing),) fusiontrees(t::AdjointTensorMap) = TensorKeyIterator(t.parent.colr, t.parent.rowr) -function Base.getindex(t::AdjointTensorMap{S, N₁, N₂, I}, - f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {S, N₁, N₂, I} +function Base.getindex(t::AdjointTensorMap{S,N₁,N₂,I}, + f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {S,N₁,N₂,I} c = f₁.coupled @boundscheck begin c == f₂.coupled || throw(SectorMismatch()) hassector(codomain(t), f₁.uncoupled) && hassector(domain(t), f₂.uncoupled) end - return sreshape( - (StridedView(t.parent.data[c])[t.parent.rowr[c][f₂], t.parent.colr[c][f₁]])', - (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...)) + return sreshape((StridedView(t.parent.data[c])[t.parent.rowr[c][f₂], + t.parent.colr[c][f₁]])', + (dims(codomain(t), f₁.uncoupled)..., dims(domain(t), f₂.uncoupled)...)) +end +@propagate_inbounds function Base.setindex!(t::AdjointTensorMap{S,N₁,N₂}, v, + f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}) where {S,N₁,N₂,I} + return copy!(getindex(t, f₁, f₂), v) end -@propagate_inbounds Base.setindex!(t::AdjointTensorMap{S, N₁, N₂}, v, - f₁::FusionTree{I, N₁}, f₂::FusionTree{I, N₂}) where {S, N₁, N₂, I} = - copy!(getindex(t, f₁, f₂), v) -@inline Base.getindex(t::AdjointTrivialTensorMap) = - sreshape(StridedView(t.parent.data)', (dims(codomain(t))..., dims(domain(t))...)) +@inline function Base.getindex(t::AdjointTrivialTensorMap) + return sreshape(StridedView(t.parent.data)', (dims(codomain(t))..., dims(domain(t))...)) +end @inline Base.setindex!(t::AdjointTrivialTensorMap, v) = copy!(getindex(t), v) -@inline Base.getindex(t::AdjointTrivialTensorMap, ::Tuple{Nothing, Nothing}) = getindex(t) -@inline Base.setindex!(t::AdjointTrivialTensorMap, v, ::Tuple{Nothing, Nothing}) = - setindex!(t, v) +@inline Base.getindex(t::AdjointTrivialTensorMap, ::Tuple{Nothing,Nothing}) = getindex(t) +@inline function Base.setindex!(t::AdjointTrivialTensorMap, v, ::Tuple{Nothing,Nothing}) + return setindex!(t, v) +end # For a tensor with trivial symmetry, allow direct indexing @inline function Base.getindex(t::AdjointTrivialTensorMap, indices::Vararg{Int}) @@ -80,7 +88,7 @@ end # Show #------ function Base.summary(t::AdjointTensorMap) - print("AdjointTensorMap(", codomain(t), " ← ", domain(t), ")") + return print("AdjointTensorMap(", codomain(t), " ← ", domain(t), ")") end function Base.show(io::IO, t::AdjointTensorMap{S}) where {S<:IndexSpace} if get(io, :compact, false) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 862769fe..54d933c9 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -7,12 +7,12 @@ Specific subtype of [`AbstractTensorMap`](@ref) for representing the braiding tensor that braids the first input over the second input; its inverse can be obtained as the adjoint. """ -struct BraidingTensor{S<:IndexSpace, A} <: AbstractTensorMap{S, 2, 2} +struct BraidingTensor{S<:IndexSpace,A} <: AbstractTensorMap{S,2,2} V1::S V2::S adjoint::Bool - function BraidingTensor{S,A}(V1::S, V2::S, adjoint::Bool = false) where - {S<:IndexSpace, A<:DenseMatrix} + function BraidingTensor{S,A}(V1::S, V2::S, + adjoint::Bool=false) where {S<:IndexSpace,A<:DenseMatrix} for a in sectors(V1) for b in sectors(V2) for c in (a ⊗ b) @@ -25,21 +25,22 @@ struct BraidingTensor{S<:IndexSpace, A} <: AbstractTensorMap{S, 2, 2} # partial construction: only construct rowr and colr when needed end end -function BraidingTensor(V1::S, V2::S, adjoint::Bool = false) where {S<:IndexSpace} +function BraidingTensor(V1::S, V2::S, adjoint::Bool=false) where {S<:IndexSpace} if BraidingStyle(sectortype(S)) isa SymmetricBraiding - return BraidingTensor{S, Matrix{Float64}}(V1, V2, adjoint) + return BraidingTensor{S,Matrix{Float64}}(V1, V2, adjoint) else - return BraidingTensor{S, Matrix{ComplexF64}}(V1, V2, adjoint) + return BraidingTensor{S,Matrix{ComplexF64}}(V1, V2, adjoint) end end -Base.adjoint(b::BraidingTensor{S,A}) where {S<:IndexSpace, A<:DenseMatrix} = - BraidingTensor(b.V1, b.V2, !b.adjoint, A) +function Base.adjoint(b::BraidingTensor{S,A}) where {S<:IndexSpace,A<:DenseMatrix} + return BraidingTensor(b.V1, b.V2, !b.adjoint, A) +end domain(b::BraidingTensor) = b.adjoint ? b.V2 ⊗ b.V1 : b.V1 ⊗ b.V2 codomain(b::BraidingTensor) = b.adjoint ? b.V1 ⊗ b.V2 : b.V2 ⊗ b.V1 -storagetype(::Type{BraidingTensor{S,A}}) where {S<:IndexSpace, A<:DenseMatrix} = A +storagetype(::Type{BraidingTensor{S,A}}) where {S<:IndexSpace,A<:DenseMatrix} = A blocksectors(b::BraidingTensor) = blocksectors(b.V1 ⊗ b.V2) hasblock(b::BraidingTensor, s::Sector) = s ∈ blocksectors(b) @@ -49,11 +50,11 @@ function fusiontrees(b::BraidingTensor) dom = domain(b) I = sectortype(b) F = fusiontreetype(I, 2) - rowr = SectorDict{I, FusionTreeDict{F, UnitRange{Int}}}() - colr = SectorDict{I, FusionTreeDict{F, UnitRange{Int}}}() + rowr = SectorDict{I,FusionTreeDict{F,UnitRange{Int}}}() + colr = SectorDict{I,FusionTreeDict{F,UnitRange{Int}}}() for c in blocksectors(codom) - rowrc = FusionTreeDict{F, UnitRange{Int}}() - colrc = FusionTreeDict{F, UnitRange{Int}}() + rowrc = FusionTreeDict{F,UnitRange{Int}}() + colrc = FusionTreeDict{F,UnitRange{Int}}() offset1 = 0 for s1 in sectors(codom) for f₁ in fusiontrees(s1, c, map(isdual, codom.spaces)) @@ -72,41 +73,44 @@ function fusiontrees(b::BraidingTensor) end end dim2 = offset2 - push!(rowr, c=>rowrc) - push!(colr, c=>colrc) + push!(rowr, c => rowrc) + push!(colr, c => colrc) end return TensorKeyIterator(rowr, colr) end -function Base.getindex(b::BraidingTensor{S}) where S +function Base.getindex(b::BraidingTensor{S}) where {S} sectortype(S) == Trivial || throw(SectorMismatch()) (V1, V2) = domain(b) d = (dim(V2), dim(V1), dim(V1), dim(V2)) return sreshape(StridedView(block(b, Trivial())), d) end -@inline function Base.getindex(b::BraidingTensor, f₁::FusionTree{I,2}, f₂::FusionTree{I,2}) where {I<:Sector} +@inline function Base.getindex(b::BraidingTensor, f₁::FusionTree{I,2}, + f₂::FusionTree{I,2}) where {I<:Sector} I == sectortype(b) || throw(SectorMismatch()) c = f₁.coupled V1, V2 = domain(b) @boundscheck begin c == f₂.coupled || throw(SectorMismatch()) - ((f₁.uncoupled[1] ∈ sectors(V2)) && (f₂.uncoupled[1] ∈ sectors(V1))) || throw(SectorMismatch()) - ((f₁.uncoupled[2] ∈ sectors(V1)) && (f₂.uncoupled[2] ∈ sectors(V2))) || throw(SectorMismatch()) + ((f₁.uncoupled[1] ∈ sectors(V2)) && (f₂.uncoupled[1] ∈ sectors(V1))) || + throw(SectorMismatch()) + ((f₁.uncoupled[2] ∈ sectors(V1)) && (f₂.uncoupled[2] ∈ sectors(V2))) || + throw(SectorMismatch()) end @inbounds begin d = (dims(V2 ⊗ V1, f₁.uncoupled)..., dims(V1 ⊗ V2, f₂.uncoupled)...) - n1 = d[1]*d[2] - n2 = d[3]*d[4] + n1 = d[1] * d[2] + n2 = d[3] * d[4] data = fill!(storagetype(b)(undef, (n1, n2)), zero(scalartype(b))) a1, a2 = f₂.uncoupled if f₁.uncoupled == (a2, a1) - braiddict = artin_braid(f₂, 1; inv = b.adjoint) + braiddict = artin_braid(f₂, 1; inv=b.adjoint) r = get(braiddict, f₁, zero(valtype(braiddict))) - si = 1 + d[1]*d[2]*d[3] - sj = d[1] + d[1]*d[2] - @inbounds for i = 1:d[1], j = 1:d[2] - data[(i-1)*si + (j-1)*sj + 1] = r + si = 1 + d[1] * d[2] * d[3] + sj = d[1] + d[1] * d[2] + @inbounds for i in 1:d[1], j in 1:d[2] + data[(i - 1) * si + (j - 1) * sj + 1] = r end end return sreshape(StridedView(data), d) @@ -125,10 +129,10 @@ function Base.copy!(t::TensorMap, b::BraidingTensor) a1, a2 = f₂.uncoupled c = f₂.coupled f₁.uncoupled == (a2, a1) || continue - braiddict = artin_braid(f₂, 1; inv = b.adjoint) + braiddict = artin_braid(f₂, 1; inv=b.adjoint) r = convert(scalartype(t), get(braiddict, f₁, zero(valtype(braiddict)))) end - for i = 1:size(data, 1), j = 1:size(data, 2) + for i in 1:size(data, 1), j in 1:size(data, 2) data[i, j, j, i] = r end end @@ -142,12 +146,12 @@ function block(b::BraidingTensor, s::Sector) (V1, V2) = domain(b) if sectortype(b) == Trivial d1, d2 = dim(V1), dim(V2) - n = d1*d2 + n = d1 * d2 data = fill!(storagetype(b)(undef, (n, n)), zero(scalartype(b))) - si = 1 + d2*d1*d1 - sj = d2 + d2*d1 - @inbounds for i = 1:d2, j = 1:d1 - data[(i-1)*si + (j-1)*sj + 1] = one(scalartype(b)) + si = 1 + d2 * d1 * d1 + sj = d2 + d2 * d1 + @inbounds for i in 1:d2, j in 1:d1 + data[(i - 1) * si + (j - 1) * sj + 1] = one(scalartype(b)) end return data end @@ -160,13 +164,13 @@ function block(b::BraidingTensor, s::Sector) d1 = dim(V1, a1) d2 = dim(V2, a2) f₁.uncoupled == (a2, a1) || continue - braiddict = artin_braid(f₂, 1; inv = b.adjoint) + braiddict = artin_braid(f₂, 1; inv=b.adjoint) r = convert(scalartype(b), get(braiddict, f₁, zero(valtype(braiddict)))) - si = 1 + n*d1 + si = 1 + n * d1 sj = d2 + n - start = first(r1) + (first(r2)-1) * n - @inbounds for i = 1:d2, j=1:d1 - data[(i-1)*si + (j-1)*sj + start] = r + start = first(r1) + (first(r2) - 1) * n + @inbounds for i in 1:d2, j in 1:d1 + data[(i - 1) * si + (j - 1) * sj + start] = r end end end @@ -183,14 +187,13 @@ function planar_contract!(C::AbstractTensorMap{S}, (p1, p2)::Index2Tuple, α::Number, β::Number, backends...) where {S} - codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' + space(B, cindB[2]) == space(A, cindA[2])' if BraidingStyle(sectortype(B)) isa Bosonic return add!(α, B, β, C, reverse(cindB), oindB) @@ -201,15 +204,14 @@ function planar_contract!(C::AbstractTensorMap{S}, elseif β != 1 rmul!(C, β) end - braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindA[1]] > braidingtensor_levels[cindA[2]] for (f₁, f₂) in fusiontrees(B) local newtrees for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) - for (f₁′′, coeff′′) in artin_braid(f₁′, 1, inv = inv_braid) - + for (f₁′′, coeff′′) in artin_braid(f₁′, 1; inv=inv_braid) f12 = (f₁′′, f₂′) - coeff = coeff′*coeff′′ + coeff = coeff′ * coeff′′ if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -217,28 +219,27 @@ function planar_contract!(C::AbstractTensorMap{S}, end end end - for ((f₁′,f₂′), coeff) in newtrees - TO._add!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], (reverse(cindB)...,oindB...)) + for ((f₁′, f₂′), coeff) in newtrees + TO._add!(coeff * α, B[f₁, f₂], true, C[f₁′, f₂′], (reverse(cindB)..., oindB...)) end end return C end function planar_contract!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, - (oindA, cindA)::Index2Tuple{<:Any,2}, - B::BraidingTensor{S}, - (cindB, oindB)::Index2Tuple{2,2}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} - + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{<:Any,2}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{2,2}, + (p1, p2)::Index2Tuple, + α::Number, β::Number, + backends...) where {S} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' + space(B, cindB[2]) == space(A, cindA[2])' if BraidingStyle(sectortype(A)) isa Bosonic return add!(α, A, β, C, oindA, reverse(cindA)) @@ -249,14 +250,14 @@ function planar_contract!(C::AbstractTensorMap{S}, elseif β != 1 rmul!(C, β) end - braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindB[1]] > braidingtensor_levels[cindB[2]] for (f₁, f₂) in fusiontrees(A) local newtrees - for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) - for (f₂′′, coeff′′) in artin_braid(f₂′, 1, inv = inv_braid) - f12 = (f₁′,f₂′′) - coeff = coeff′*conj(coeff′′) + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + for (f₂′′, coeff′′) in artin_braid(f₂′, 1; inv=inv_braid) + f12 = (f₁′, f₂′′) + coeff = coeff′ * conj(coeff′′) if @isdefined newtrees newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff else @@ -264,11 +265,11 @@ function planar_contract!(C::AbstractTensorMap{S}, end end end - for ((f₁′,f₂′), coeff) in newtrees - TO._add!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], (oindA...,reverse(cindA)...)) + for ((f₁′, f₂′), coeff) in newtrees + TO._add!(coeff * α, A[f₁, f₂], true, C[f₁′, f₂′], (oindA..., reverse(cindA)...)) end end - C + return C end function planar_contract!(C::AbstractTensorMap{S}, @@ -279,16 +280,15 @@ function planar_contract!(C::AbstractTensorMap{S}, (p1, p2)::Index2Tuple, α::Number, β::Number, backends...) where {S} - codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' && - space(B, cindB[3]) == space(A, cindA[3])' && - space(B, cindB[4]) == space(A, cindA[4])' + space(B, cindB[2]) == space(A, cindA[2])' && + space(B, cindB[3]) == space(A, cindA[3])' && + space(B, cindB[4]) == space(A, cindA[4])' if BraidingStyle(sectortype(B)) isa Bosonic return trace!(α, B, β, C, (), oindB, (cindB[1], cindB[2]), (cindB[3], cindB[4])) @@ -302,11 +302,11 @@ function planar_contract!(C::AbstractTensorMap{S}, I = sectortype(B) u = one(I) f₀ = FusionTree{I}((), u, (), (), ()) - braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] for (f₁, f₂) in fusiontrees(B) local newtrees - for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) f₁′.coupled == u || continue a = f₁′.uncoupled[1] b = f₁′.uncoupled[2] @@ -315,7 +315,7 @@ function planar_contract!(C::AbstractTensorMap{S}, # should be automatic by matching spaces: # f₁′.isdual[1] != f₁′.isdual[3] || continue # f₁′.isdual[2] != f₁′.isdual[4] || continue - for (f₁′′, coeff′′) in artin_braid(f₁′, 2, inv = inv_braid) + for (f₁′′, coeff′′) in artin_braid(f₁′, 2; inv=inv_braid) f₁′′.innerlines[1] == u || continue coeff = coeff′ * coeff′′ * sqrtdim(a) * sqrtdim(b) if f₁′′.isdual[1] @@ -334,8 +334,8 @@ function planar_contract!(C::AbstractTensorMap{S}, end @isdefined(newtrees) || continue for ((f₁′, f₂′), coeff) in newtrees - TO._trace!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], oindB, - (cindB[1], cindB[2]), (cindB[3], cindB[4])) + TO._trace!(coeff * α, B[f₁, f₂], true, C[f₁′, f₂′], oindB, + (cindB[1], cindB[2]), (cindB[3], cindB[4])) end end return C @@ -349,16 +349,15 @@ function planar_contract!(C::AbstractTensorMap{S}, (p1, p2)::Index2Tuple, α::Number, β::Number, backends...) where {S} - codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' && - space(B, cindB[3]) == space(A, cindA[3])' && - space(B, cindB[4]) == space(A, cindA[4])' + space(B, cindB[2]) == space(A, cindA[2])' && + space(B, cindB[3]) == space(A, cindA[3])' && + space(B, cindB[4]) == space(A, cindA[4])' if BraidingStyle(sectortype(B)) isa Bosonic return trace!(α, A, β, C, oindA, (), (cindA[1], cindA[2]), (cindA[3], cindA[4])) @@ -372,7 +371,7 @@ function planar_contract!(C::AbstractTensorMap{S}, I = sectortype(B) u = one(I) f₀ = FusionTree{I}((), u, (), (), ()) - braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] for (f₁, f₂) in fusiontrees(A) local newtrees @@ -385,7 +384,7 @@ function planar_contract!(C::AbstractTensorMap{S}, # should be automatic by matching spaces: # f₂′.isdual[1] != f₂′.isdual[3] || continue # f₂′.isdual[3] != f₂′.isdual[4] || continue - for (f₂′′, coeff′′) in artin_braid(f₂′, 2, inv = inv_braid) + for (f₂′′, coeff′′) in artin_braid(f₂′, 2; inv=inv_braid) f₂′′.innerlines[1] == u || continue coeff = coeff′ * conj(coeff′′ * sqrtdim(a) * sqrtdim(b)) if f₂′′.isdual[1] @@ -404,8 +403,8 @@ function planar_contract!(C::AbstractTensorMap{S}, end @isdefined(newtrees) || continue for ((f₁′, f₂′), coeff) in newtrees - TO._trace!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], oindA, - (cindA[1], cindA[2]), (cindA[3], cindA[4])) + TO._trace!(coeff * α, A[f₁, f₂], true, C[f₁′, f₂′], oindA, + (cindA[1], cindA[2]), (cindA[3], cindA[4])) end end return C @@ -421,12 +420,12 @@ function planar_contract!(C::AbstractTensorMap{S}, backends...) where {S} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' && - space(B, cindB[3]) == space(A, cindA[3])' + space(B, cindB[2]) == space(A, cindA[2])' && + space(B, cindB[3]) == space(A, cindA[3])' if BraidingStyle(sectortype(B)) isa Bosonic return trace!(α, B, β, C, (cindB[2],), oindB, (cindB[1],), (cindB[3],)) @@ -439,18 +438,18 @@ function planar_contract!(C::AbstractTensorMap{S}, end I = sectortype(B) u = one(I) - braidingtensor_levels = A.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] for (f₁, f₂) in fusiontrees(B) local newtrees - for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) a = f₁′.uncoupled[1] b = f₁′.uncoupled[2] b == f₁′.coupled || continue a == dual(f₁′.uncoupled[3]) || continue # should be automatic by matching spaces: # f₁′.isdual[1] != f₁.isdual[3] || continue - for (f₁′′, coeff′′) in artin_braid(f₁′, 2, inv = inv_braid) + for (f₁′′, coeff′′) in artin_braid(f₁′, 2; inv=inv_braid) f₁′′.innerlines[1] == u || continue coeff = coeff′ * coeff′′ * sqrtdim(a) if f₁′′.isdual[1] @@ -466,9 +465,9 @@ function planar_contract!(C::AbstractTensorMap{S}, end end @isdefined(newtrees) || continue - for ((f₁′,f₂′), coeff) in newtrees - TO._trace!(coeff*α, B[f₁, f₂], true, C[f₁′,f₂′], - (cindB[2], oindB...) , (cindB[1],), (cindB[3],)) + for ((f₁′, f₂′), coeff) in newtrees + TO._trace!(coeff * α, B[f₁, f₂], true, C[f₁′, f₂′], + (cindB[2], oindB...), (cindB[1],), (cindB[3],)) end end return C @@ -482,15 +481,14 @@ function planar_contract!(C::AbstractTensorMap{S}, (p1, p2)::Index2Tuple, α::Number, β::Number, backends...) where {S} - codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) - oindA, cindA, oindB, cindB = - reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) + oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, + oindB, cindB, p1, p2) @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' && - space(B, cindB[3]) == space(A, cindA[3])' + space(B, cindB[2]) == space(A, cindA[2])' && + space(B, cindB[3]) == space(A, cindA[3])' if BraidingStyle(sectortype(A)) isa Bosonic return trace!(α, A, β, C, oindA, (cindA[2],), (cindA[1],), (cindA[3],)) @@ -503,18 +501,18 @@ function planar_contract!(C::AbstractTensorMap{S}, end I = sectortype(B) u = one(I) - braidingtensor_levels = B.adjoint ? (1,2,2,1) : (2,1,1,2) + braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] for (f₁, f₂) in fusiontrees(A) local newtrees - for ((f₁′,f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) a = f₂′.uncoupled[1] b = f₂′.uncoupled[2] b == f₂′.coupled || continue a == dual(f₂′.uncoupled[3]) || continue # should be automatic by matching spaces: # f₂′.isdual[1] != f₂.isdual[3] || continue - for (f₂′′, coeff′′) in artin_braid(f₂′, 2, inv = inv_braid) + for (f₂′′, coeff′′) in artin_braid(f₂′, 2; inv=inv_braid) f₂′′.innerlines[1] == u || continue coeff = coeff′ * conj(coeff′′ * sqrtdim(a)) if f₂′′.isdual[1] @@ -530,9 +528,9 @@ function planar_contract!(C::AbstractTensorMap{S}, end end @isdefined(newtrees) || continue - for ((f₁′,f₂′), coeff) in newtrees - TO._trace!(coeff*α, A[f₁, f₂], true, C[f₁′,f₂′], - (oindA...,cindA[2]) , (cindA[1],), (cindA[3],)) + for ((f₁′, f₂′), coeff) in newtrees + TO._trace!(coeff * α, A[f₁, f₂], true, C[f₁′, f₂′], + (oindA..., cindA[2]), (cindA[1],), (cindA[3],)) end end return C diff --git a/src/tensors/factorizations.jl b/src/tensors/factorizations.jl index 2b2d0f31..0c7a9009 100644 --- a/src/tensors/factorizations.jl +++ b/src/tensors/factorizations.jl @@ -3,20 +3,17 @@ const OFA = OrthogonalFactorizationAlgorithm import LinearAlgebra: svd!, svd -const SVDAlg = Union{SVD, SDD} - -Base.@deprecate( - svd(t::AbstractTensorMap, leftind::IndexTuple, rightind::IndexTuple; - trunc::TruncationScheme = notrunc(), p::Real = 2, alg::SVDAlg = SDD()), - tsvd(t, leftind, rightind; trunc = trunc, p = p, alg = alg)) -Base.@deprecate( - svd(t::AbstractTensorMap; - trunc::TruncationScheme = notrunc(), p::Real = 2, alg::SVDAlg = SDD()), - tsvd(t; trunc = trunc, p = p, alg = alg)) -Base.@deprecate( - svd!(t::AbstractTensorMap; - trunc::TruncationScheme = notrunc(), p::Real = 2, alg::SVDAlg = SDD()), - tsvd(t; trunc = trunc, p = p, alg = alg)) +const SVDAlg = Union{SVD,SDD} + +Base.@deprecate(svd(t::AbstractTensorMap, leftind::IndexTuple, rightind::IndexTuple; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t, leftind, rightind; trunc=trunc, p=p, alg=alg)) +Base.@deprecate(svd(t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t; trunc=trunc, p=p, alg=alg)) +Base.@deprecate(svd!(t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t; trunc=trunc, p=p, alg=alg)) """ tsvd(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; @@ -50,8 +47,9 @@ algorithm that computes the decomposition (`_gesvd` or `_gesdd`). Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `tsvd(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -tsvd(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - tsvd!(permute(t, (p1, p2); copy = true); kwargs...) +function tsvd(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return tsvd!(permute(t, (p1, p2); copy=true); kwargs...) +end """ leftorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; @@ -68,15 +66,16 @@ Different algorithms are available, namely `QR()`, `QRpos()`, `SVD()` and `Polar 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 uniqe (no residual freedom) so that they always return the same +`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) === EuclideanProduct()`. """ -leftorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - leftorth!(permute(t, (p1, p2); copy = true); kwargs...) +function leftorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return leftorth!(permute(t, (p1, p2); copy=true); kwargs...) +end """ rightorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; @@ -95,15 +94,16 @@ a QR decomposition of the transpose. `RQ()` and `RQpos()` produce an upper trian 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 uniqe (no residual freedom) so -that they always return the same result for the same input tensor `t`. +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) === EuclideanProduct()`. """ -rightorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - rightorth!(permute(t, (p1, p2); copy = true); kwargs...) +function rightorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return rightorth!(permute(t, (p1, p2); copy=true); kwargs...) +end """ leftnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; @@ -127,8 +127,9 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `leftnull(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - leftnull!(permute(t, (p1, p2); copy = true); kwargs...) +function leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return leftnull!(permute(t, (p1, p2); copy=true); kwargs...) +end """ rightnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; @@ -154,8 +155,9 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `rightnull(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -rightnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - rightnull!(permute(t, (p1, p2); copy = true); kwargs...) +function rightnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return rightnull!(permute(t, (p1, p2); copy=true); kwargs...) +end """ eigen(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V @@ -176,8 +178,10 @@ matrices. See the corresponding documentation for more information. See also `eig` and `eigh` """ -LinearAlgebra.eigen(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - eigen!(permute(t, (p1, p2); copy = true); kwargs...) +function LinearAlgebra.eigen(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; + kwargs...) + return eigen!(permute(t, (p1, p2); copy=true); kwargs...) +end """ eig(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V @@ -200,8 +204,9 @@ Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of See also `eigen` and `eigh`. """ -eig(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) = - eig!(permute(t, (p1, p2); copy = true); kwargs...) +function eig(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) + return eig!(permute(t, (p1, p2); copy=true); kwargs...) +end """ eigh(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple) -> D, V @@ -241,20 +246,26 @@ meaningless Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of dense matrices. See the corresponding documentation for more information. """ -LinearAlgebra.isposdef(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) = - isposdef!(permute(t, (p1, p2); copy = true)) - -tsvd(t::AbstractTensorMap; trunc::TruncationScheme = NoTruncation(), - p::Real = 2, alg::Union{SVD, SDD} = SDD()) = - tsvd!(copy(t); trunc = trunc, p = p, alg = alg) -leftorth(t::AbstractTensorMap; alg::OFA = QRpos(), kwargs...) = - leftorth!(copy(t); alg = alg, kwargs...) -rightorth(t::AbstractTensorMap; alg::OFA = LQpos(), kwargs...) = - rightorth!(copy(t); alg = alg, kwargs...) -leftnull(t::AbstractTensorMap; alg::OFA = QR(), kwargs...) = - leftnull!(copy(t); alg = alg, kwargs...) -rightnull(t::AbstractTensorMap; alg::OFA = LQ(), kwargs...) = - rightnull!(copy(t); alg = alg, kwargs...) +function LinearAlgebra.isposdef(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) + return isposdef!(permute(t, (p1, p2); copy=true)) +end + +function tsvd(t::AbstractTensorMap; trunc::TruncationScheme=NoTruncation(), + p::Real=2, alg::Union{SVD,SDD}=SDD()) + return tsvd!(copy(t); trunc=trunc, p=p, alg=alg) +end +function leftorth(t::AbstractTensorMap; alg::OFA=QRpos(), kwargs...) + return leftorth!(copy(t); alg=alg, kwargs...) +end +function rightorth(t::AbstractTensorMap; alg::OFA=LQpos(), kwargs...) + return rightorth!(copy(t); alg=alg, kwargs...) +end +function leftnull(t::AbstractTensorMap; alg::OFA=QR(), kwargs...) + return leftnull!(copy(t); alg=alg, kwargs...) +end +function rightnull(t::AbstractTensorMap; alg::OFA=LQ(), kwargs...) + return rightnull!(copy(t); alg=alg, kwargs...) +end LinearAlgebra.eigen(t::AbstractTensorMap; kwargs...) = eigen!(copy(t); kwargs...) eig(t::AbstractTensorMap; kwargs...) = eig!(copy(t); kwargs...) eigh(t::AbstractTensorMap; kwargs...) = eigh!(copy(t); kwargs...) @@ -308,16 +319,16 @@ function leftorth!(t::TensorMap; InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("leftorth! only defined for Euclidean inner product spaces")) if !iszero(rtol) - atol = max(atol, rtol*norm(t)) + atol = max(atol, rtol * norm(t)) end I = sectortype(t) S = spacetype(t) A = storagetype(t) - Qdata = SectorDict{I, A}() - Rdata = SectorDict{I, A}() - dims = SectorDict{I, Int}() + Qdata = SectorDict{I,A}() + Rdata = SectorDict{I,A}() + dims = SectorDict{I,Int}() for c in blocksectors(domain(t)) - isempty(block(t,c)) && continue + isempty(block(t, c)) && continue Q, R = _leftorth!(block(t, c), alg, atol) Qdata[c] = Q Rdata[c] = R @@ -334,7 +345,7 @@ function leftorth!(t::TensorMap; else W = ProductSpace(V) end - return TensorMap(Qdata, codomain(t)←W), TensorMap(Rdata, W←domain(t)) + return TensorMap(Qdata, codomain(t) ← W), TensorMap(Rdata, W ← domain(t)) end function leftnull!(t::TensorMap; @@ -345,21 +356,21 @@ function leftnull!(t::TensorMap; InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("leftnull! only defined for Euclidean inner product spaces")) if !iszero(rtol) - atol = max(atol, rtol*norm(t)) + atol = max(atol, rtol * norm(t)) end I = sectortype(t) S = spacetype(t) A = storagetype(t) V = codomain(t) - Ndata = SectorDict{I, A}() - dims = SectorDict{I, Int}() + Ndata = SectorDict{I,A}() + dims = SectorDict{I,Int}() for c in blocksectors(V) N = _leftnull!(block(t, c), alg, atol) Ndata[c] = N dims[c] = size(N, 2) end W = S(dims) - return TensorMap(Ndata, V←W) + return TensorMap(Ndata, V ← W) end function rightorth!(t::TensorMap; @@ -370,16 +381,16 @@ function rightorth!(t::TensorMap; InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("rightorth! only defined for Euclidean inner product spaces")) if !iszero(rtol) - atol = max(atol, rtol*norm(t)) + atol = max(atol, rtol * norm(t)) end I = sectortype(t) S = spacetype(t) A = storagetype(t) - Ldata = SectorDict{I, A}() - Qdata = SectorDict{I, A}() - dims = SectorDict{I, Int}() + Ldata = SectorDict{I,A}() + Qdata = SectorDict{I,A}() + dims = SectorDict{I,Int}() for c in blocksectors(codomain(t)) - isempty(block(t,c)) && continue + isempty(block(t, c)) && continue L, Q = _rightorth!(block(t, c), alg, atol) Ldata[c] = L Qdata[c] = Q @@ -396,7 +407,7 @@ function rightorth!(t::TensorMap; else W = ProductSpace(V) end - return TensorMap(Ldata, codomain(t)←W), TensorMap(Qdata, W←domain(t)) + return TensorMap(Ldata, codomain(t) ← W), TensorMap(Qdata, W ← domain(t)) end function rightnull!(t::TensorMap; @@ -407,21 +418,21 @@ function rightnull!(t::TensorMap; InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("rightnull! only defined for Euclidean inner product spaces")) if !iszero(rtol) - atol = max(atol, rtol*norm(t)) + atol = max(atol, rtol * norm(t)) end I = sectortype(t) S = spacetype(t) A = storagetype(t) V = domain(t) - Ndata = SectorDict{I, A}() - dims = SectorDict{I, Int}() + Ndata = SectorDict{I,A}() + dims = SectorDict{I,Int}() for c in blocksectors(V) N = _rightnull!(block(t, c), alg, atol) Ndata[c] = N dims[c] = size(N, 1) end W = S(dims) - return TensorMap(Ndata, W←V) + return TensorMap(Ndata, W ← V) end function tsvd!(t::TensorMap; @@ -434,15 +445,15 @@ function tsvd!(t::TensorMap; I = sectortype(t) A = storagetype(t) Ar = similarstoragetype(t, real(scalartype(t))) - Udata = SectorDict{I, A}() - Σmdata = SectorDict{I, Ar}() # this will contain the singular values as matrix - Vdata = SectorDict{I, A}() - dims = SectorDict{sectortype(t), Int}() + Udata = SectorDict{I,A}() + Σmdata = SectorDict{I,Ar}() # this will contain the singular values as matrix + Vdata = SectorDict{I,A}() + dims = SectorDict{sectortype(t),Int}() if isempty(blocksectors(t)) W = S(dims) truncerr = zero(real(scalartype(t))) - return TensorMap(Udata, codomain(t)←W), TensorMap(Σmdata, W←W), - TensorMap(Vdata, W←domain(t)), truncerr + return TensorMap(Udata, codomain(t) ← W), TensorMap(Σmdata, W ← W), + TensorMap(Vdata, W ← domain(t)), truncerr end for (c, b) in blocks(t) U, Σ, V = _svd!(b, alg) @@ -451,13 +462,13 @@ function tsvd!(t::TensorMap; if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction Σdata[c] = Σ else - Σdata = SectorDict(c=>Σ) + Σdata = SectorDict(c => Σ) end dims[c] = length(Σ) end if !isa(trunc, NoTruncation) Σdata, truncerr = _truncate!(Σdata, trunc, p) - truncdims = SectorDict{I, Int}() + truncdims = SectorDict{I,Int}() for c in blocksectors(t) truncdim = length(Σdata[c]) if truncdim != 0 @@ -486,8 +497,8 @@ function tsvd!(t::TensorMap; for (c, Σ) in Σdata Σmdata[c] = copyto!(similar(Σ, length(Σ), length(Σ)), Diagonal(Σ)) end - return TensorMap(Udata, codomain(t)←W), TensorMap(Σmdata, W←W), - TensorMap(Vdata, W←domain(t)), truncerr + return TensorMap(Udata, codomain(t) ← W), TensorMap(Σmdata, W ← W), + TensorMap(Vdata, W ← domain(t)), truncerr end function LinearAlgebra.ishermitian(t::TensorMap) @@ -510,9 +521,9 @@ function eigh!(t::TensorMap; kwargs...) I = sectortype(t) A = storagetype(t) Ar = similarstoragetype(t, real(scalartype(t))) - Ddata = SectorDict{I, Ar}() - Vdata = SectorDict{I, A}() - dims = SectorDict{I, Int}() + Ddata = SectorDict{I,Ar}() + Vdata = SectorDict{I,A}() + dims = SectorDict{I,Int}() for (c, b) in blocks(t) values, vectors = eigen!(Hermitian(b); kwargs...) d = length(values) @@ -525,7 +536,7 @@ function eigh!(t::TensorMap; kwargs...) else W = S(dims) end - return TensorMap(Ddata, W←W), TensorMap(Vdata, domain(t)←W) + return TensorMap(Ddata, W ← W), TensorMap(Vdata, domain(t) ← W) end function eig!(t::TensorMap; kwargs...) @@ -535,9 +546,9 @@ function eig!(t::TensorMap; kwargs...) I = sectortype(t) T = complex(scalartype(t)) Ac = similarstoragetype(t, T) - Ddata = SectorDict{I, Ac}() - Vdata = SectorDict{I, Ac}() - dims = SectorDict{I, Int}() + Ddata = SectorDict{I,Ac}() + Vdata = SectorDict{I,Ac}() + dims = SectorDict{I,Int}() for (c, b) in blocks(t) values, vectors = eigen!(b; kwargs...) d = length(values) @@ -554,7 +565,7 @@ function eig!(t::TensorMap; kwargs...) else W = S(dims) end - return TensorMap(Ddata, W←W), TensorMap(Vdata, domain(t)←W) + return TensorMap(Ddata, W ← W), TensorMap(Vdata, domain(t) ← W) end function LinearAlgebra.isposdef!(t::TensorMap) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 047f5b00..3545bfc8 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -15,20 +15,25 @@ end function Base.:*(t::AbstractTensorMap, α::Number) return mul!(similar(t, promote_type(scalartype(t), typeof(α))), t, α) end -Base.:*(α::Number, t::AbstractTensorMap) = - mul!(similar(t, promote_type(scalartype(t), typeof(α))), α, t) -Base.:/(t::AbstractTensorMap, α::Number) = *(t, one(scalartype(t))/α) -Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(scalartype(t))/α) +function Base.:*(α::Number, t::AbstractTensorMap) + return mul!(similar(t, promote_type(scalartype(t), typeof(α))), α, t) +end +Base.:/(t::AbstractTensorMap, α::Number) = *(t, one(scalartype(t)) / α) +Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(scalartype(t)) / α) -LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real = 2) = rmul!(t, inv(norm(t, p))) -LinearAlgebra.normalize(t::AbstractTensorMap, p::Real = 2) = - mul!(similar(t), t, inv(norm(t, p))) +LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real=2) = rmul!(t, inv(norm(t, p))) +function LinearAlgebra.normalize(t::AbstractTensorMap, p::Real=2) + return mul!(similar(t), t, inv(norm(t, p))) +end -Base.:*(t1::AbstractTensorMap, t2::AbstractTensorMap) = - mul!(similar(t1, promote_type(scalartype(t1), scalartype(t2)), codomain(t1)←domain(t2)), t1, t2) +function Base.:*(t1::AbstractTensorMap, t2::AbstractTensorMap) + return mul!(similar(t1, promote_type(scalartype(t1), scalartype(t2)), + codomain(t1) ← domain(t2)), t1, t2) +end Base.exp(t::AbstractTensorMap) = exp!(copy(t)) -Base.:^(t::AbstractTensorMap, p::Integer) = - p < 0 ? Base.power_by_squaring(inv(t), -p) : Base.power_by_squaring(t, p) +function Base.:^(t::AbstractTensorMap, p::Integer) + return p < 0 ? Base.power_by_squaring(inv(t), -p) : Base.power_by_squaring(t, p) +end # Special purpose constructors #------------------------------ @@ -54,8 +59,9 @@ Construct the identity endomorphism on space `space`, i.e. return a `t::TensorMa """ id(A, V::ElementarySpace) = id(A, ProductSpace(V)) id(V::VectorSpace) = id(Matrix{Float64}, V) -id(::Type{A}, P::ProductSpace) where {A<:DenseMatrix} = - one!(TensorMap(s->A(undef, s), P, P)) +function id(::Type{A}, P::ProductSpace) where {A<:DenseMatrix} + return one!(TensorMap(s -> A(undef, s), P, P)) +end """ isomorphism([A::Type{<:DenseMatrix} = Matrix{Float64},] @@ -73,13 +79,15 @@ See also [`unitary`](@ref) when `InnerProductStyle(cod) === EuclideanProduct()`. """ isomorphism(cod::TensorSpace, dom::TensorSpace) = isomorphism(Matrix{Float64}, cod, dom) isomorphism(P::TensorMapSpace) = isomorphism(codomain(P), domain(P)) -isomorphism(A::Type{<:DenseMatrix}, P::TensorMapSpace) = - isomorphism(A, codomain(P), domain(P)) -isomorphism(A::Type{<:DenseMatrix}, cod::TensorSpace, dom::TensorSpace) = - isomorphism(A, convert(ProductSpace, cod), convert(ProductSpace, dom)) +function isomorphism(A::Type{<:DenseMatrix}, P::TensorMapSpace) + return isomorphism(A, codomain(P), domain(P)) +end +function isomorphism(A::Type{<:DenseMatrix}, cod::TensorSpace, dom::TensorSpace) + return isomorphism(A, convert(ProductSpace, cod), convert(ProductSpace, dom)) +end function isomorphism(::Type{A}, cod::ProductSpace, dom::ProductSpace) where {A<:DenseMatrix} cod ≅ dom || throw(SpaceMismatch("codomain $cod and domain $dom are not isomorphic")) - t = TensorMap(s->A(undef, s), cod, dom) + t = TensorMap(s -> A(undef, s), cod, dom) for (c, b) in blocks(t) _one!(b) end @@ -99,7 +107,7 @@ for a specific isomorphism, but the current choice is such that `unitary(cod, dom) == inv(unitary(dom, cod)) = adjoint(unitary(dom, cod))`. """ function unitary(cod::TensorSpace{S}, dom::TensorSpace{S}) where {S} - InnerProductStyle(S) === EuclideanProduct() || + InnerProductStyle(S) === EuclideanProduct() || throw(ArgumentError("unitary requires inner product spaces")) return isomorphism(cod, dom) end @@ -133,15 +141,17 @@ inclusion, an error will be thrown. isometry(cod::TensorSpace, dom::TensorSpace) = isometry(Matrix{Float64}, cod, dom) isometry(P::TensorMapSpace) = isometry(codomain(P), domain(P)) isometry(A::Type{<:DenseMatrix}, P::TensorMapSpace) = isometry(A, codomain(P), domain(P)) -isometry(A::Type{<:DenseMatrix}, cod::TensorSpace, dom::TensorSpace) = - isometry(A, convert(ProductSpace, cod), convert(ProductSpace, dom)) +function isometry(A::Type{<:DenseMatrix}, cod::TensorSpace, dom::TensorSpace) + return isometry(A, convert(ProductSpace, cod), convert(ProductSpace, dom)) +end function isometry(::Type{A}, - cod::ProductSpace{S}, - dom::ProductSpace{S}) where {A<:DenseMatrix, S<:ElementarySpace} + cod::ProductSpace{S}, + dom::ProductSpace{S}) where {A<:DenseMatrix,S<:ElementarySpace} InnerProductStyle(S) === EuclideanProduct() || throw(ArgumentError("isometries require Euclidean inner product")) - dom ≾ cod || throw(SpaceMismatch("codomain $cod and domain $dom do not allow for an isometric mapping")) - t = TensorMap(s->A(undef, s), cod, dom) + dom ≾ cod || + throw(SpaceMismatch("codomain $cod and domain $dom do not allow for an isometric mapping")) + t = TensorMap(s -> A(undef, s), cod, dom) for (c, b) in blocks(t) _one!(b) end @@ -177,42 +187,54 @@ function LinearAlgebra.adjoint!(tdst::AbstractTensorMap, end # Basic vector space methods: recycle VectorInterface implementation -LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) = iszero(α) ? zerovector!(t) : scale!(t, α) -LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) = iszero(α) ? zerovector!(t) : scale!(t, α) +function LinearAlgebra.rmul!(t::AbstractTensorMap, α::Number) + return iszero(α) ? zerovector!(t) : scale!(t, α) +end +function LinearAlgebra.lmul!(α::Number, t::AbstractTensorMap) + return iszero(α) ? zerovector!(t) : scale!(t, α) +end -LinearAlgebra.mul!(t1::AbstractTensorMap, t2::AbstractTensorMap, α::Number) = scale!(t1, t2, α) -LinearAlgebra.mul!(t1::AbstractTensorMap, α::Number, t2::AbstractTensorMap) = scale!(t1, t2, α) +function LinearAlgebra.mul!(t1::AbstractTensorMap, t2::AbstractTensorMap, α::Number) + return scale!(t1, t2, α) +end +function LinearAlgebra.mul!(t1::AbstractTensorMap, α::Number, t2::AbstractTensorMap) + return scale!(t1, t2, α) +end # TODO: remove VectorInterface namespace when we renamed TensorKit.add! -LinearAlgebra.axpy!(α::Number, t1::AbstractTensorMap, t2::AbstractTensorMap) = - VectorInterface.add!(t2, t1, α) -LinearAlgebra.axpby!(α::Number, t1::AbstractTensorMap, β::Number, t2::AbstractTensorMap) = - VectorInterface.add!(t2, t1, α, β) +function LinearAlgebra.axpy!(α::Number, t1::AbstractTensorMap, t2::AbstractTensorMap) + return VectorInterface.add!(t2, t1, α) +end +function LinearAlgebra.axpby!(α::Number, t1::AbstractTensorMap, β::Number, + t2::AbstractTensorMap) + return VectorInterface.add!(t2, t1, α, β) +end # inner product and norm only valid for spaces with Euclidean inner product LinearAlgebra.dot(t1::AbstractTensorMap, t2::AbstractTensorMap) = inner(t1, t2) -function LinearAlgebra.norm(t::AbstractTensorMap, p::Real = 2) +function LinearAlgebra.norm(t::AbstractTensorMap, p::Real=2) InnerProductStyle(t) === EuclideanProduct() || throw(ArgumentError("norm requires Euclidean inner product")) return _norm(blocks(t), p, float(zero(real(scalartype(t))))) end function _norm(blockiter, p::Real, init::Real) if p == Inf - return mapreduce(max, blockiter; init = init) do (c, b) - isempty(b) ? init : oftype(init, LinearAlgebra.normInf(b)) + return mapreduce(max, blockiter; init=init) do (c, b) + return isempty(b) ? init : oftype(init, LinearAlgebra.normInf(b)) end elseif p == 2 - return sqrt(mapreduce(+, blockiter; init = init) do (c, b) - isempty(b) ? init : oftype(init, dim(c)*LinearAlgebra.norm2(b)^2) - end) + return sqrt(mapreduce(+, blockiter; init=init) do (c, b) + return isempty(b) ? init : + oftype(init, dim(c) * LinearAlgebra.norm2(b)^2) + end) elseif p == 1 - return mapreduce(+, blockiter; init = init) do (c, b) - isempty(b) ? init : oftype(init, dim(c)*sum(abs, b)) + return mapreduce(+, blockiter; init=init) do (c, b) + return isempty(b) ? init : oftype(init, dim(c) * sum(abs, b)) end elseif p > 0 - s = mapreduce(+, blockiter; init = init) do (c, b) - isempty(b) ? init : oftype(init, dim(c)*LinearAlgebra.normp(b, p)^p) + s = mapreduce(+, blockiter; init=init) do (c, b) + return isempty(b) ? init : oftype(init, dim(c) * LinearAlgebra.normp(b, p)^p) end return s^inv(oftype(s, p)) else @@ -225,14 +247,15 @@ end function LinearAlgebra.tr(t::AbstractTensorMap) domain(t) == codomain(t) || throw(SpaceMismatch("Trace of a tensor only exist when domain == codomain")) - return sum(dim(c)*tr(b) for (c, b) in blocks(t)) + return sum(dim(c) * tr(b) for (c, b) in blocks(t)) end # TensorMap multiplication function LinearAlgebra.mul!(tC::AbstractTensorMap, tA::AbstractTensorMap, - tB::AbstractTensorMap, α = true, β = false) - if !(codomain(tC) == codomain(tA) && domain(tC) == domain(tB) && domain(tA) == codomain(tB)) + tB::AbstractTensorMap, α=true, β=false) + if !(codomain(tC) == codomain(tA) && domain(tC) == domain(tB) && + domain(tA) == codomain(tB)) throw(SpaceMismatch("$(space(tC)) ≠ $(space(tA)) * $(space(tB))")) end for c in blocksectors(tC) @@ -257,24 +280,24 @@ function Base.inv(t::AbstractTensorMap) throw(SpaceMismatch("codomain $cod and domain $dom are not isomorphic: no inverse")) end if sectortype(t) === Trivial - return TensorMap(inv(block(t, Trivial())), domain(t)←codomain(t)) + return TensorMap(inv(block(t, Trivial())), domain(t) ← codomain(t)) else data = empty(t.data) for (c, b) in blocks(t) data[c] = inv(b) end - return TensorMap(data, domain(t)←codomain(t)) + return TensorMap(data, domain(t) ← codomain(t)) end end function LinearAlgebra.pinv(t::AbstractTensorMap; kwargs...) if sectortype(t) === Trivial - return TensorMap(pinv(block(t, Trivial()); kwargs...), domain(t)←codomain(t)) + return TensorMap(pinv(block(t, Trivial()); kwargs...), domain(t) ← codomain(t)) else data = empty(t.data) for (c, b) in blocks(t) data[c] = pinv(b; kwargs...) end - return TensorMap(data, domain(t)←codomain(t)) + return TensorMap(data, domain(t) ← codomain(t)) end end function Base.:(\)(t1::AbstractTensorMap, t2::AbstractTensorMap) @@ -282,11 +305,12 @@ function Base.:(\)(t1::AbstractTensorMap, t2::AbstractTensorMap) throw(SpaceMismatch("non-matching codomains in t1 \\ t2")) if sectortype(t1) === Trivial data = block(t1, Trivial()) \ block(t2, Trivial()) - return TensorMap(data, domain(t1)←domain(t2)) + return TensorMap(data, domain(t1) ← domain(t2)) else cod = codomain(t1) - data = SectorDict(c=>block(t1, c) \ block(t2, c) for c in blocksectors(codomain(t1))) - return TensorMap(data, domain(t1)←domain(t2)) + data = SectorDict(c => block(t1, c) \ block(t2, c) + for c in blocksectors(codomain(t1))) + return TensorMap(data, domain(t1) ← domain(t2)) end end function Base.:(/)(t1::AbstractTensorMap, t2::AbstractTensorMap) @@ -294,10 +318,11 @@ function Base.:(/)(t1::AbstractTensorMap, t2::AbstractTensorMap) throw(SpaceMismatch("non-matching domains in t1 / t2")) if sectortype(t1) === Trivial data = block(t1, Trivial()) / block(t2, Trivial()) - return TensorMap(data, codomain(t1)←codomain(t2)) + return TensorMap(data, codomain(t1) ← codomain(t2)) else - data = SectorDict(c=>block(t1, c) / block(t2, c) for c in blocksectors(domain(t1))) - return TensorMap(data, codomain(t1)←codomain(t2)) + data = SectorDict(c => block(t1, c) / block(t2, c) + for c in blocksectors(domain(t1))) + return TensorMap(data, codomain(t1) ← codomain(t2)) end end @@ -313,14 +338,14 @@ end # Sylvester equation with TensorMap objects: function LinearAlgebra.sylvester(A::AbstractTensorMap, - B::AbstractTensorMap, - C::AbstractTensorMap) + B::AbstractTensorMap, + C::AbstractTensorMap) (codomain(A) == domain(A) == codomain(C) && codomain(B) == domain(B) == domain(C)) || throw(SpaceMismatch()) cod = domain(A) dom = codomain(B) sylABC(c) = sylvester(block(A, c), block(B, c), block(C, c)) - data = SectorDict(c=>sylABC(c) for c in blocksectors(cod ← dom)) + data = SectorDict(c => sylABC(c) for c in blocksectors(cod ← dom)) return TensorMap(data, cod ← dom) end @@ -342,9 +367,9 @@ for f in (:cos, :sin, :tan, :cot, :cosh, :sinh, :tanh, :coth, :atan, :acot, :asi return TensorMap(data, codomain(t), domain(t)) else if scalartype(t) <: Real - datadict = SectorDict{I, T}(c=>real($f(b)) for (c, b) in blocks(t)) + datadict = SectorDict{I,T}(c => real($f(b)) for (c, b) in blocks(t)) else - datadict = SectorDict{I, T}(c=>$f(b) for (c, b) in blocks(t)) + datadict = SectorDict{I,T}(c => $f(b) for (c, b) in blocks(t)) end return TensorMap(datadict, codomain(t), domain(t)) end @@ -362,14 +387,15 @@ for f in (:sqrt, :log, :asin, :acos, :acosh, :atanh, :acoth) data::T = $f(block(t, Trivial())) return TensorMap(data, codomain(t), domain(t)) else - datadict = SectorDict{I, T}(c=>$f(b) for (c, b) in blocks(t)) + datadict = SectorDict{I,T}(c => $f(b) for (c, b) in blocks(t)) return TensorMap(datadict, codomain(t), domain(t)) end end end # concatenate tensors -function catdomain(t1::AbstractTensorMap{S, N₁, 1}, t2::AbstractTensorMap{S, N₁, 1}) where {S, N₁} +function catdomain(t1::AbstractTensorMap{S,N₁,1}, + t2::AbstractTensorMap{S,N₁,1}) where {S,N₁} codomain(t1) == codomain(t2) || throw(SpaceMismatch()) V1, = domain(t1) @@ -385,7 +411,8 @@ function catdomain(t1::AbstractTensorMap{S, N₁, 1}, t2::AbstractTensorMap{S, N end return t end -function catcodomain(t1::AbstractTensorMap{S, 1, N₂}, t2::AbstractTensorMap{S, 1, N₂}) where {S, N₂} +function catcodomain(t1::AbstractTensorMap{S,1,N₂}, + t2::AbstractTensorMap{S,1,N₂}) where {S,N₂} domain(t1) == domain(t2) || throw(SpaceMismatch()) V1, = codomain(t1) @@ -410,7 +437,7 @@ Compute the tensor product between two `AbstractTensorMap` instances, which resu new `TensorMap` instance whose codomain is `codomain(t1) ⊗ codomain(t2)` and whose domain is `domain(t1) ⊗ domain(t2)`. """ -function ⊗(t1::AbstractTensorMap{S}, t2::AbstractTensorMap{S}) where S +function ⊗(t1::AbstractTensorMap{S}, t2::AbstractTensorMap{S}) where {S} cod1, cod2 = codomain(t1), codomain(t2) dom1, dom2 = domain(t1), domain(t2) cod = cod1 ⊗ cod2 @@ -432,7 +459,7 @@ function ⊗(t1::AbstractTensorMap{S}, t2::AbstractTensorMap{S}) where S c2 = f2l.coupled # = f2r.coupled for c in c1 ⊗ c2 degeneracyiter = FusionStyle(c) isa GenericFusion ? - (1:Nsymbol(c1, c2, c)) : (nothing,) + (1:Nsymbol(c1, c2, c)) : (nothing,) for μ in degeneracyiter for (fl, coeff1) in merge(f1l, f2l, c, μ) for (fr, coeff2) in merge(f1r, f2r, c, μ) @@ -462,11 +489,11 @@ function ⊠(t1::AbstractTensorMap, t2::AbstractTensorMap) I2 = sectortype(S2) codom1 = codomain(t1) ⊠ one(S2) dom1 = domain(t1) ⊠ one(S2) - data1 = SectorDict{I1 ⊠ I2, storagetype(t1)}(c ⊠ one(I2) => b for (c,b) in blocks(t1)) + data1 = SectorDict{I1 ⊠ I2,storagetype(t1)}(c ⊠ one(I2) => b for (c, b) in blocks(t1)) t1′ = TensorMap(data1, codom1, dom1) codom2 = one(S1) ⊠ codomain(t2) dom2 = one(S1) ⊠ domain(t2) - data2 = SectorDict{I1 ⊠ I2, storagetype(t2)}(one(I1) ⊠ c => b for (c,b) in blocks(t2)) + data2 = SectorDict{I1 ⊠ I2,storagetype(t2)}(one(I1) ⊠ c => b for (c, b) in blocks(t2)) t2′ = TensorMap(data2, codom2, dom2) return t1′ ⊗ t2′ end diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 490cc1e0..5a828327 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -1,6 +1,7 @@ # TensorMap & Tensor: # general tensor implementation with arbitrary symmetries #==========================================================# +#! format: off """ struct TensorMap{S<:IndexSpace, N₁, N₂, ...} <: AbstractTensorMap{S, N₁, N₂} @@ -20,21 +21,24 @@ struct TensorMap{S<:IndexSpace, N₁, N₂, I<:Sector, A<:Union{<:DenseMatrix,Se {S<:IndexSpace, N₁, N₂, I<:Sector, A<:SectorDict{I,<:DenseMatrix}, F₁<:FusionTree{I,N₁}, F₂<:FusionTree{I,N₂}} T = scalartype(valtype(data)) - T ⊆ field(S) || @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog=1) - new{S, N₁, N₂, I, A, F₁, F₂}(data, codom, dom, rowr, colr) + T ⊆ field(S) || @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog = 1) + return new{S,N₁,N₂,I,A,F₁,F₂}(data, codom, dom, rowr, colr) end - function TensorMap{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data::A, - codom::ProductSpace{S,N₁}, dom::ProductSpace{S,N₂}) where - {S<:IndexSpace, N₁, N₂, A<:DenseMatrix} + function TensorMap{S,N₁,N₂,Trivial,A,Nothing,Nothing}(data::A, + codom::ProductSpace{S,N₁}, + dom::ProductSpace{S,N₂}) where + {S<:IndexSpace,N₁,N₂,A<:DenseMatrix} T = scalartype(data) T ⊆ field(S) || - @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog=1) - new{S, N₁, N₂, Trivial, A, Nothing, Nothing}(data, codom, dom) + @warn("scalartype(data) = $T ⊈ $(field(S)))", maxlog = 1) + return new{S,N₁,N₂,Trivial,A,Nothing,Nothing}(data, codom, dom) end end +#! format: on -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} +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} function tensormaptype(::Type{S}, N₁::Int, N₂::Int, ::Type{T}) where {S,T} I = sectortype(S) @@ -53,7 +57,7 @@ function tensormaptype(::Type{S}, N₁::Int, N₂::Int, ::Type{T}) where {S,T} return TensorMap{S,N₁,N₂,I,SectorDict{I,M},F₁,F₂} end end -tensormaptype(S, N₁, N₂ = 0) = tensormaptype(S, N₁, N₂, Float64) +tensormaptype(S, N₁, N₂=0) = tensormaptype(S, N₁, N₂, Float64) # Basic methods for characterising a tensor: #-------------------------------------------- @@ -63,12 +67,16 @@ domain(t::TensorMap) = t.dom blocksectors(t::TrivialTensorMap) = OneOrNoneIterator(dim(t) != 0, Trivial()) blocksectors(t::TensorMap) = keys(t.data) -storagetype(::Type{<:TensorMap{<:IndexSpace,N₁,N₂,Trivial,A}}) where - {N₁,N₂,A<:DenseMatrix} = A -storagetype(::Type{<:TensorMap{<:IndexSpace,N₁,N₂,I,<:SectorDict{I,A}}}) where - {N₁,N₂,I<:Sector,A<:DenseMatrix} = A +function storagetype(::Type{<:TensorMap{<:IndexSpace,N₁,N₂,Trivial,A}}) where + {N₁,N₂,A<:DenseMatrix} + return A +end +function storagetype(::Type{<:TensorMap{<:IndexSpace,N₁,N₂,I,<:SectorDict{I,A}}}) where + {N₁,N₂,I<:Sector,A<:DenseMatrix} + return A +end -dim(t::TensorMap) = mapreduce(x->length(x[2]), +, blocks(t); init = 0) +dim(t::TensorMap) = mapreduce(x -> length(x[2]), +, blocks(t); init=0) # General TensorMap constructors #-------------------------------- @@ -158,57 +166,62 @@ function _buildblockstructure(P::ProductSpace{S,N}, blocksectors) where {S<:Inde return treeranges, blockdims end -TensorMap(f, - ::Type{T}, - codom::ProductSpace{S}, - dom::ProductSpace{S}) where {S<:IndexSpace, T<:Number} = - TensorMap(d->f(T, d), codom, dom) +function TensorMap(f, ::Type{T}, codom::ProductSpace{S}, + dom::ProductSpace{S}) where {S<:IndexSpace,T<:Number} + return TensorMap(d -> f(T, d), codom, dom) +end -TensorMap(::Type{T}, - codom::ProductSpace{S}, - dom::ProductSpace{S}) where {S<:IndexSpace, T<:Number} = - TensorMap(d->Array{T}(undef, d), codom, dom) +function TensorMap(::Type{T}, codom::ProductSpace{S}, + dom::ProductSpace{S}) where {S<:IndexSpace,T<:Number} + return TensorMap(d -> Array{T}(undef, d), codom, dom) +end -TensorMap(::UndefInitializer, - ::Type{T}, - codom::ProductSpace{S}, - dom::ProductSpace{S}) where {S<:IndexSpace, T<:Number} = - TensorMap(d->Array{T}(undef, d), codom, dom) +function TensorMap(::UndefInitializer, ::Type{T}, codom::ProductSpace{S}, + dom::ProductSpace{S}) where {S<:IndexSpace,T<:Number} + return TensorMap(d -> Array{T}(undef, d), codom, dom) +end -TensorMap(::UndefInitializer, - codom::ProductSpace{S}, - dom::ProductSpace{S}) where {S<:IndexSpace} = - TensorMap(undef, Float64, codom, dom) +function TensorMap(::UndefInitializer, codom::ProductSpace{S}, + dom::ProductSpace{S}) where {S<:IndexSpace} + return TensorMap(undef, Float64, codom, dom) +end -TensorMap(::Type{T}, - codom::TensorSpace{S}, - dom::TensorSpace{S}) where {T<:Number, S<:IndexSpace} = - TensorMap(T, convert(ProductSpace, codom), convert(ProductSpace, dom)) +function TensorMap(::Type{T}, codom::TensorSpace{S}, + dom::TensorSpace{S}) where {T<:Number,S<:IndexSpace} + return TensorMap(T, convert(ProductSpace, codom), convert(ProductSpace, dom)) +end -TensorMap(dataorf, codom::TensorSpace{S}, dom::TensorSpace{S}) where {S<:IndexSpace} = - TensorMap(dataorf, convert(ProductSpace, codom), convert(ProductSpace, dom)) +function TensorMap(dataorf, codom::TensorSpace{S}, + dom::TensorSpace{S}) where {S<:IndexSpace} + return TensorMap(dataorf, convert(ProductSpace, codom), convert(ProductSpace, dom)) +end -TensorMap(dataorf, ::Type{T}, - codom::TensorSpace{S}, - dom::TensorSpace{S}) where {T<:Number, S<:IndexSpace} = - TensorMap(dataorf, T, convert(ProductSpace, codom), convert(ProductSpace, dom)) +function TensorMap(dataorf, ::Type{T}, codom::TensorSpace{S}, + dom::TensorSpace{S}) where {T<:Number,S<:IndexSpace} + return TensorMap(dataorf, T, convert(ProductSpace, codom), convert(ProductSpace, dom)) +end -TensorMap(codom::TensorSpace{S}, dom::TensorSpace{S}) where {S<:IndexSpace} = - TensorMap(Float64, convert(ProductSpace, codom), convert(ProductSpace, dom)) +function TensorMap(codom::TensorSpace{S}, dom::TensorSpace{S}) where {S<:IndexSpace} + return TensorMap(Float64, convert(ProductSpace, codom), convert(ProductSpace, dom)) +end -TensorMap(dataorf, T::Type{<:Number}, P::TensorMapSpace{S}) where {S<:IndexSpace} = - TensorMap(dataorf, T, codomain(P), domain(P)) +function TensorMap(dataorf, T::Type{<:Number}, P::TensorMapSpace{S}) where {S<:IndexSpace} + return TensorMap(dataorf, T, codomain(P), domain(P)) +end -TensorMap(dataorf, P::TensorMapSpace{S}) where {S<:IndexSpace} = - TensorMap(dataorf, codomain(P), domain(P)) +function TensorMap(dataorf, P::TensorMapSpace{S}) where {S<:IndexSpace} + return TensorMap(dataorf, codomain(P), domain(P)) +end -TensorMap(T::Type{<:Number}, P::TensorMapSpace{S}) where {S<:IndexSpace} = - TensorMap(T, codomain(P), domain(P)) +function TensorMap(T::Type{<:Number}, P::TensorMapSpace{S}) where {S<:IndexSpace} + return TensorMap(T, codomain(P), domain(P)) +end TensorMap(P::TensorMapSpace{S}) where {S<:IndexSpace} = TensorMap(codomain(P), domain(P)) -Tensor(dataorf, T::Type{<:Number}, P::TensorSpace{S}) where {S<:IndexSpace} = - TensorMap(dataorf, T, P, one(P)) +function Tensor(dataorf, T::Type{<:Number}, P::TensorSpace{S}) where {S<:IndexSpace} + return TensorMap(dataorf, T, P, one(P)) +end Tensor(dataorf, P::TensorSpace{S}) where {S<:IndexSpace} = TensorMap(dataorf, P, one(P)) @@ -355,7 +368,8 @@ function Base.similar(t::TensorMap{S}, ::Type{T}, P::TensorMapSpace{S}) where {T colr, coldims = _buildblockstructure(domain(P), blocksectoriterator) end M = similarstoragetype(t, T) - data = SectorDict{I,M}(c => M(undef, (rowdims[c], coldims[c])) for c in blocksectoriterator) + data = SectorDict{I,M}(c => M(undef, (rowdims[c], coldims[c])) + for c in blocksectoriterator) A = typeof(data) return TensorMap{S,N₁,N₂,I,A,F₁,F₂}(data, codomain(P), domain(P), rowr, colr) end @@ -375,7 +389,7 @@ function Base.convert(::Type{Dict}, t::AbstractTensorMap) d[:codomain] = repr(codomain(t)) d[:domain] = repr(domain(t)) data = Dict{String,Any}() - for (c,b) in blocks(t) + for (c, b) in blocks(t) data[repr(c)] = Array(b) end d[:data] = data @@ -385,12 +399,12 @@ function Base.convert(::Type{TensorMap}, d::Dict{Symbol,Any}) try codomain = eval(Meta.parse(d[:codomain])) domain = eval(Meta.parse(d[:domain])) - data = SectorDict(eval(Meta.parse(c))=>b for (c,b) in d[:data]) + data = SectorDict(eval(Meta.parse(c)) => b for (c, b) in d[:data]) return TensorMap(data, codomain, domain) catch e # sector unknown in TensorKit.jl; user-defined, hopefully accessible in Main codomain = Base.eval(Main, Meta.parse(d[:codomain])) domain = Base.eval(Main, Meta.parse(d[:domain])) - data = SectorDict(Base.eval(Main, Meta.parse(c))=>b for (c,b) in d[:data]) + data = SectorDict(Base.eval(Main, Meta.parse(c)) => b for (c, b) in d[:data]) return TensorMap(data, codomain, domain) end end @@ -407,20 +421,20 @@ function block(t::TensorMap, s::Sector) if haskey(t.data, s) return t.data[s] else # at least one of the two matrix dimensions will be zero - return storagetype(t)(undef, (blockdim(codomain(t),s), blockdim(domain(t), s))) + return storagetype(t)(undef, (blockdim(codomain(t), s), blockdim(domain(t), s))) end end -blocks(t::TensorMap{<:IndexSpace,N₁,N₂,Trivial}) where {N₁,N₂} = - SingletonDict(Trivial()=>t.data) +function blocks(t::TensorMap{<:IndexSpace,N₁,N₂,Trivial}) where {N₁,N₂} + return SingletonDict(Trivial() => t.data) +end blocks(t::TensorMap) = t.data fusiontrees(t::TrivialTensorMap) = ((nothing, nothing),) fusiontrees(t::TensorMap) = TensorKeyIterator(t.rowr, t.colr) @inline function Base.getindex(t::TensorMap{<:IndexSpace,N₁,N₂,I}, - sectors::Tuple{Vararg{I}}) where {N₁,N₂,I<:Sector} - + sectors::Tuple{Vararg{I}}) where {N₁,N₂,I<:Sector} FusionStyle(I) isa UniqueFusion || throw(SectorMismatch("Indexing with sectors only possible if unique fusion")) s1 = TupleTools.getindices(sectors, codomainind(t)) @@ -434,15 +448,16 @@ fusiontrees(t::TensorMap) = TensorKeyIterator(t.rowr, t.colr) f₁ = FusionTree(s1, c1, map(isdual, tuple(codomain(t)...))) f₂ = FusionTree(s2, c1, map(isdual, tuple(domain(t)...))) @inbounds begin - return t[f₁,f₂] + return t[f₁, f₂] end end -@propagate_inbounds Base.getindex(t::TensorMap, sectors::Tuple) = - t[map(sectortype(t), sectors)] +@propagate_inbounds function Base.getindex(t::TensorMap, sectors::Tuple) + return t[map(sectortype(t), sectors)] +end @inline function Base.getindex(t::TensorMap{<:IndexSpace,N₁,N₂,I}, - f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} - + f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} c = f₁.coupled @boundscheck begin c == f₂.coupled || throw(SectorMismatch()) @@ -454,15 +469,17 @@ end return sreshape(StridedView(t.data[c])[t.rowr[c][f₁], t.colr[c][f₂]], d) end end -@propagate_inbounds Base.setindex!(t::TensorMap{<:IndexSpace,N₁,N₂,I}, - v, - f₁::FusionTree{I,N₁}, - f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} = - copy!(getindex(t, f₁, f₂), v) +@propagate_inbounds function Base.setindex!(t::TensorMap{<:IndexSpace,N₁,N₂,I}, + v, + f₁::FusionTree{I,N₁}, + f₂::FusionTree{I,N₂}) where {N₁,N₂,I<:Sector} + return copy!(getindex(t, f₁, f₂), v) +end # For a tensor with trivial symmetry, allow no argument indexing -@inline Base.getindex(t::TrivialTensorMap) = - sreshape(StridedView(t.data), (dims(codomain(t))..., dims(domain(t))...)) +@inline function Base.getindex(t::TrivialTensorMap) + return sreshape(StridedView(t.data), (dims(codomain(t))..., dims(domain(t))...)) +end @inline Base.setindex!(t::TrivialTensorMap, v) = copy!(getindex(t), v) # For a tensor with trivial symmetry, fusiontrees returns (nothing,nothing) @@ -486,7 +503,7 @@ end # Show #------ function Base.summary(t::TensorMap) - print("TensorMap(", codomain(t), " ← ", domain(t), ")") + return print("TensorMap(", codomain(t), " ← ", domain(t), ")") end function Base.show(io::IO, t::TensorMap{S}) where {S<:IndexSpace} if get(io, :compact, false) @@ -498,15 +515,15 @@ function Base.show(io::IO, t::TensorMap{S}) where {S<:IndexSpace} Base.print_array(io, t[]) println(io) elseif FusionStyle(sectortype(S)) isa UniqueFusion - for (f₁,f₂) in fusiontrees(t) + for (f₁, f₂) in fusiontrees(t) println(io, "* Data for sector ", f₁.uncoupled, " ← ", f₂.uncoupled, ":") - Base.print_array(io, t[f₁,f₂]) + Base.print_array(io, t[f₁, f₂]) println(io) end else - for (f₁,f₂) in fusiontrees(t) + for (f₁, f₂) in fusiontrees(t) println(io, "* Data for fusiontree ", f₁, " ← ", f₂, ":") - Base.print_array(io, t[f₁,f₂]) + Base.print_array(io, t[f₁, f₂]) println(io) end end @@ -543,15 +560,16 @@ end # Conversion and promotion: #--------------------------- Base.convert(::Type{TensorMap}, t::TensorMap) = t -Base.convert(::Type{TensorMap}, t::AbstractTensorMap) = - copy!(TensorMap(undef, scalartype(t), codomain(t), domain(t)), t) +function Base.convert(::Type{TensorMap}, t::AbstractTensorMap) + return copy!(TensorMap(undef, scalartype(t), codomain(t), domain(t)), t) +end function Base.convert(T::Type{TensorMap{S,N₁,N₂,I,A,F₁,F₂}}, - t::AbstractTensorMap{S,N₁,N₂}) where {S,N₁,N₂,I,A,F₁,F₂} + t::AbstractTensorMap{S,N₁,N₂}) where {S,N₁,N₂,I,A,F₁,F₂} if typeof(t) == T return t else - data = Dict(c=>convert(storagetype(T), b) for (c,b) in blocks(t)) + data = Dict(c => convert(storagetype(T), b) for (c, b) in blocks(t)) return TensorMap(data, codomain(t), domain(t)) end end diff --git a/src/tensors/tensortreeiterator.jl b/src/tensors/tensortreeiterator.jl index 55783e40..83057ebb 100644 --- a/src/tensors/tensortreeiterator.jl +++ b/src/tensors/tensortreeiterator.jl @@ -1,18 +1,20 @@ -struct TensorKeyIterator{I<:Sector, F₁<:FusionTree{I}, F₂<:FusionTree{I}} - rowr::SectorDict{I, FusionTreeDict{F₁, UnitRange{Int}}} - colr::SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}} +struct TensorKeyIterator{I<:Sector,F₁<:FusionTree{I},F₂<:FusionTree{I}} + rowr::SectorDict{I,FusionTreeDict{F₁,UnitRange{Int}}} + colr::SectorDict{I,FusionTreeDict{F₂,UnitRange{Int}}} end -struct TensorPairIterator{I<:Sector, F₁<:FusionTree{I}, F₂<:FusionTree{I}, A<:DenseMatrix} - rowr::SectorDict{I, FusionTreeDict{F₁, UnitRange{Int}}} - colr::SectorDict{I, FusionTreeDict{F₂, UnitRange{Int}}} - data::SectorDict{I, A} +struct TensorPairIterator{I<:Sector,F₁<:FusionTree{I},F₂<:FusionTree{I},A<:DenseMatrix} + rowr::SectorDict{I,FusionTreeDict{F₁,UnitRange{Int}}} + colr::SectorDict{I,FusionTreeDict{F₂,UnitRange{Int}}} + data::SectorDict{I,A} end - -const TensorIterator{I<:Sector, F₁<:FusionTree{I}, F₂<:FusionTree{I}} = Union{TensorKeyIterator{I, F₁, F₂}, TensorPairIterator{I, F₁, F₂}} +#! format: off +const TensorIterator{I<:Sector,F₁<:FusionTree{I},F₂<:FusionTree{I}} = + Union{TensorKeyIterator{I,F₁,F₂},TensorPairIterator{I,F₁,F₂}} +#! format: on Base.IteratorSize(::Type{<:TensorIterator}) = Base.HasLength() Base.IteratorEltype(::Type{<:TensorIterator}) = Base.HasEltype() -Base.eltype(T::Type{TensorKeyIterator{I, F₁, F₂}}) where {I, F₁, F₂} = Tuple{F₁, F₂} +Base.eltype(T::Type{TensorKeyIterator{I,F₁,F₂}}) where {I,F₁,F₂} = Tuple{F₁,F₂} function Base.length(t::TensorKeyIterator) l = 0 diff --git a/src/tensors/truncation.jl b/src/tensors/truncation.jl index e480406e..f35efea2 100644 --- a/src/tensors/truncation.jl +++ b/src/tensors/truncation.jl @@ -28,13 +28,12 @@ struct TruncationCutoff{T<:Real} <: TruncationScheme end truncbelow(epsilon::Real, add_back::Int=0) = TruncationCutoff(epsilon, add_back) - # For a single vector -function _truncate!(v::AbstractVector, ::NoTruncation, p::Real = 2) +function _truncate!(v::AbstractVector, ::NoTruncation, p::Real=2) return v, zero(real(eltype(v))) end -function _truncate!(v::AbstractVector, trunc::TruncationError, p::Real = 2) +function _truncate!(v::AbstractVector, trunc::TruncationError, p::Real=2) S = real(eltype(v)) truncerr = zero(S) dmax = length(v) @@ -42,7 +41,7 @@ function _truncate!(v::AbstractVector, trunc::TruncationError, p::Real = 2) while dtrunc > 0 dtrunc -= 1 prevtruncerr = truncerr - truncerr = norm(view(v, dtrunc+1:dmax), p) + truncerr = norm(view(v, (dtrunc + 1):dmax), p) if truncerr > trunc.ϵ dtrunc += 1 truncerr = prevtruncerr @@ -53,36 +52,37 @@ function _truncate!(v::AbstractVector, trunc::TruncationError, p::Real = 2) return v, truncerr end -function _truncate!(v::AbstractVector, trunc::TruncationDimension, p::Real = 2) +function _truncate!(v::AbstractVector, trunc::TruncationDimension, p::Real=2) S = real(eltype(v)) dtrunc = min(length(v), trunc.dim) - truncerr = norm(view(v, dtrunc+1:length(v)), p) + truncerr = norm(view(v, (dtrunc + 1):length(v)), p) resize!(v, dtrunc) return v, truncerr end -_truncate!(v::AbstractVector, trunc::TruncationSpace, p::Real = 2) = - _truncate!(v, truncdim(dim(trunc.space)), p) +function _truncate!(v::AbstractVector, trunc::TruncationSpace, p::Real=2) + return _truncate!(v, truncdim(dim(trunc.space)), p) +end ######################## ######################## -function _truncate!(v::AbstractVector, trunc::TruncationCutoff, p::Real = 2) +function _truncate!(v::AbstractVector, trunc::TruncationCutoff, p::Real=2) S = real(eltype(v)) dtrunc = findlast(Base.Fix2(>, trunc.ϵ), v) if dtrunc === nothing dtrunc = 0 end dtrunc = min(dtrunc + trunc.add_back, length(v)) - truncerr = norm(view(v, dtrunc+1:length(v)), p) + truncerr = norm(view(v, (dtrunc + 1):length(v)), p) resize!(v, dtrunc) return v, truncerr end # For SectorDict -const SectorVectorDict{I<:Sector} = SectorDict{I, <:AbstractVector} +const SectorVectorDict{I<:Sector} = SectorDict{I,<:AbstractVector} function _findnexttruncvalue(V::SectorVectorDict{I}, - truncdim::SectorDict{I, Int}, p::Real) where {I<:Sector} + truncdim::SectorDict{I,Int}, p::Real) where {I<:Sector} S = real(eltype(valtype(V))) q = convert(S, p) it = keys(V) @@ -95,12 +95,12 @@ function _findnexttruncvalue(V::SectorVectorDict{I}, c, s = next end cmin = c - vmin::S = convert(S, dim(c))^inv(q)*V[c][truncdim[c]] + vmin::S = convert(S, dim(c))^inv(q) * V[c][truncdim[c]] next = iterate(it, s) while next !== nothing c, s = next if truncdim[c] > 0 - v = dim(c)^inv(q)*V[c][truncdim[c]] + v = dim(c)^inv(q) * V[c][truncdim[c]] if v < vmin cmin, vmin = c, v end @@ -110,21 +110,22 @@ function _findnexttruncvalue(V::SectorVectorDict{I}, return cmin end -function _truncate!(V::SectorVectorDict, ::NoTruncation, p = 2) +function _truncate!(V::SectorVectorDict, ::NoTruncation, p=2) S = real(eltype(valtype(V))) return V, zero(S) end -function _truncate!(V::SectorVectorDict, trunc::TruncationError, p = 2) +function _truncate!(V::SectorVectorDict, trunc::TruncationError, p=2) I = keytype(V) S = real(eltype(valtype(V))) - truncdim = SectorDict{I, Int}(c=>length(v) for (c, v) in V) + truncdim = SectorDict{I,Int}(c => length(v) for (c, v) in V) truncerr = zero(S) while true cmin = _findnexttruncvalue(V, truncdim, p) cmin === nothing && break truncdim[cmin] -= 1 prevtruncerr = truncerr - truncerr = _norm((c=>view(v, truncdim[c]+1:length(v)) for (c, v) in V), p, zero(S)) + truncerr = _norm((c => view(v, (truncdim[c] + 1):length(v)) for (c, v) in V), p, + zero(S)) if truncerr > trunc.ϵ truncdim[cmin] += 1 truncerr = prevtruncerr @@ -136,26 +137,28 @@ function _truncate!(V::SectorVectorDict, trunc::TruncationError, p = 2) end return V, truncerr end -function _truncate!(V::SectorVectorDict, trunc::TruncationDimension, p = 2) +function _truncate!(V::SectorVectorDict, trunc::TruncationDimension, p=2) I = keytype(V) S = real(eltype(valtype(V))) - truncdim = SectorDict{I, Int}(c=>length(v) for (c, v) in V) - while sum(dim(c)*d for (c, d) in truncdim) > trunc.dim + truncdim = SectorDict{I,Int}(c => length(v) for (c, v) in V) + while sum(dim(c) * d for (c, d) in truncdim) > trunc.dim cmin = _findnexttruncvalue(V, truncdim, p) cmin === nothing && break truncdim[cmin] -= 1 end - truncerr = _norm((c=>view(v, truncdim[c]+1:length(v)) for (c, v) in V), p, zero(S)) + truncerr = _norm((c => view(v, (truncdim[c] + 1):length(v)) for (c, v) in V), p, + zero(S)) for (c, v) in V resize!(v, truncdim[c]) end return V, truncerr end -function _truncate!(V::SectorVectorDict, trunc::TruncationSpace, p = 2) +function _truncate!(V::SectorVectorDict, trunc::TruncationSpace, p=2) I = keytype(V) S = real(eltype(valtype(V))) - truncdim = SectorDict{I, Int}(c=>min(length(v), dim(trunc.space, c)) for (c, v) in V) - truncerr = _norm((c=>view(v, truncdim[c]+1:length(v)) for (c, v) in V), p, zero(S)) + truncdim = SectorDict{I,Int}(c => min(length(v), dim(trunc.space, c)) for (c, v) in V) + truncerr = _norm((c => view(v, (truncdim[c] + 1):length(v)) for (c, v) in V), p, + zero(S)) for c in keys(V) resize!(V[c], truncdim[c]) end @@ -163,28 +166,29 @@ function _truncate!(V::SectorVectorDict, trunc::TruncationSpace, p = 2) end ######################## ######################## -function _truncate!(V::SectorVectorDict, trunc::TruncationCutoff, p = 2) +function _truncate!(V::SectorVectorDict, trunc::TruncationCutoff, p=2) I = keytype(V) S = real(eltype(valtype(V))) - truncdim = SectorDict{I, Int }(c=>length(v) for (c, v) in V) - next_val = SectorDict{I, Array{S} }(c=>[zero(S)] for (c, v) in V) + truncdim = SectorDict{I,Int}(c => length(v) for (c, v) in V) + next_val = SectorDict{I,Array{S}}(c => [zero(S)] for (c, v) in V) for (c, v) in V newdim = findlast(Base.Fix2(>, trunc.ϵ), v) if newdim === nothing truncdim[c] = 0 - next_val[c] = v - else - truncdim[c] = newdim - next_val[c] = v[newdim+1:end] + next_val[c] = v + else + truncdim[c] = newdim + next_val[c] = v[(newdim + 1):end] end end - for i in 1:trunc.add_back + for i in 1:(trunc.add_back) key_max = argmax(next_val) - length(next_val[key_max]) == 0 && break + length(next_val[key_max]) == 0 && break truncdim[key_max] += 1 next_val[key_max] = next_val[key_max][2:end] end - truncerr = _norm((c=>view(v, truncdim[c]+1:length(v)) for (c, v) in V), p, zero(S)) + truncerr = _norm((c => view(v, (truncdim[c] + 1):length(v)) for (c, v) in V), p, + zero(S)) for (c, v) in V resize!(v, truncdim[c]) end @@ -195,24 +199,27 @@ end struct MultipleTruncation{T<:Tuple{Vararg{TruncationScheme}}} <: TruncationScheme truncations::T end -Base.:&(a::MultipleTruncation, b::MultipleTruncation) = - MultipleTruncation((a.truncations..., b.truncations...)) -Base.:&(a::MultipleTruncation, b::TruncationScheme) = - MultipleTruncation((a.truncations..., b)) -Base.:&(a::TruncationScheme, b::MultipleTruncation) = - MultipleTruncation((a, b.truncations...)) +function Base.:&(a::MultipleTruncation, b::MultipleTruncation) + return MultipleTruncation((a.truncations..., b.truncations...)) +end +function Base.:&(a::MultipleTruncation, b::TruncationScheme) + return MultipleTruncation((a.truncations..., b)) +end +function Base.:&(a::TruncationScheme, b::MultipleTruncation) + return MultipleTruncation((a, b.truncations...)) +end Base.:&(a::TruncationScheme, b::TruncationScheme) = MultipleTruncation((a, b)) -function _truncate!(v, trunc::MultipleTruncation, p::Real = 2) +function _truncate!(v, trunc::MultipleTruncation, p::Real=2) v, truncerrs = __truncate!(v, trunc.truncations, p) return v, norm(truncerrs, p) end -function __truncate!(v, trunc::Tuple{Vararg{TruncationScheme}}, p::Real = 2) +function __truncate!(v, trunc::Tuple{Vararg{TruncationScheme}}, p::Real=2) v, truncerr1 = _truncate!(v, first(trunc), p) v, truncerrtail = __truncate!(v, tail(trunc), p) return v, (truncerr1, truncerrtail...) end -function __truncate!(v, trunc::Tuple{<:TruncationScheme}, p::Real = 2) +function __truncate!(v, trunc::Tuple{<:TruncationScheme}, p::Real=2) v, truncerr1 = _truncate!(v, first(trunc), p) return v, (truncerr1,) end diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index 9562aea8..dcae5284 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -20,9 +20,9 @@ VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) # scale, scale! & scale!! #------------------------- -VectorInterface.scale(t::TensorMap, α::ONumber) = _isone(α) ? t : t * α +VectorInterface.scale(t::TensorMap, α::ONumber) = _isone(α) ? t : t * α function VectorInterface.scale!(t::AbstractTensorMap, α::ONumber) - for (c,b) in blocks(t) + for (c, b) in blocks(t) scale!(b, α) end return t @@ -56,19 +56,22 @@ end # add, add! & add!! #------------------- # TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed -function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) +function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, + β::ONumber=_one) space(ty) == space(tx) || throw(SpaceMismatch()) T = promote_type(scalartype(ty), scalartype(tx), typeof(α), typeof(β)) return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end -function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) +function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, + α::ONumber=_one, β::ONumber=_one) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) VectorInterface.add!(block(ty, c), block(tx, c), α, β) end return ty end -function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, β::ONumber=_one) +function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, + α::ONumber=_one, β::ONumber=_one) T = scalartype(ty) if promote_type(T, typeof(α), typeof(β), scalartype(tx)) <: T return VectorInterface.add!(ty, tx, α, β) @@ -89,4 +92,4 @@ function VectorInterface.inner(tx::AbstractTensorMap, ty::AbstractTensorMap) s += convert(T, dim(c)) * dot(block(tx, c), block(ty, c)) end return s -end \ No newline at end of file +end diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index af5aeaf5..61dc7d38 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -2,7 +2,8 @@ println("------------------------------------") println("Fusion Trees") println("------------------------------------") ti = time() -@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose=true for I in sectorlist +@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in + sectorlist Istr = TensorKit.type_repr(I) N = 5 out = ntuple(n -> randsector(I), N) @@ -48,7 +49,7 @@ ti = time() return t′ end braid_i_to_1 = braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...)) - trees2 = Dict(_reinsert_partial_tree(t,f2)=>c for (t,c) in braid_i_to_1) + trees2 = Dict(_reinsert_partial_tree(t, f2) => c for (t, c) in braid_i_to_1) trees3 = empty(trees2) p = (((N + 1):(N + i - 1))..., (1:N)..., ((N + i):(2N - 1))...) levels = ((i:(N + i - 1))..., (1:(i - 1))..., ((i + N):(2N - 1))...) diff --git a/test/planar.jl b/test/planar.jl index ff958752..02f58166 100644 --- a/test/planar.jl +++ b/test/planar.jl @@ -50,56 +50,59 @@ end @test force_planar(tensortrace!(C, p, A, q, :N, true, true)) ≈ planartrace!(C′, A′, p, q, true, true) end - + @testset "planarcontract" begin A = TensorMap(randn, ℂ^2 ⊗ ℂ^3 ← ℂ^2 ⊗ ℂ^5 ⊗ ℂ^4) B = TensorMap(randn, ℂ^2 ⊗ ℂ^4 ← ℂ^4 ⊗ ℂ^3) C = TensorMap(randn, (ℂ^5)' ⊗ (ℂ^2)' ⊗ ℂ^2 ← (ℂ^2)' ⊗ ℂ^4) - + A′ = force_planar(A) B′ = force_planar(B) C′ = force_planar(C) - + pA = ((1, 3, 4), (5, 2)) pB = ((2, 4), (1, 3)) pAB = ((3, 2, 1), (4, 5)) - + @test force_planar(tensorcontract!(C, pAB, A, pA, :N, B, pB, :N, true, true)) ≈ - planarcontract!(C′, A′, pA, B′, pB, pAB, true, true) + planarcontract!(C′, A′, pA, B′, pB, pAB, true, true) end end -@testset "@planar" verbose=true begin +@testset "@planar" verbose = true begin T = ComplexF64 @testset "MPS networks" begin P = ℂ^2 Vmps = ℂ^12 Vmpo = ℂ^4 - + # ∂AC # ------- x = TensorMap(randn, T, Vmps ⊗ P ← Vmps) O = TensorMap(randn, T, Vmpo ⊗ P ← P ⊗ Vmpo) GL = TensorMap(randn, T, Vmps ⊗ Vmpo' ← Vmps) GR = TensorMap(randn, T, Vmps ⊗ Vmpo ← Vmps) - + x′ = force_planar(x) O′ = force_planar(O) GL′ = force_planar(GL) GR′ = force_planar(GR) - + @tensor y[-1 -2; -3] := GL[-1 2; 1] * x[1 3; 4] * O[2 -2; 3 5] * GR[4 5; -3] @planar y′[-1 -2; -3] := GL′[-1 2; 1] * x′[1 3; 4] * O′[2 -2; 3 5] * GR′[4 5; -3] @test force_planar(y) ≈ y′ - + # ∂AC2 # ------- x2 = TensorMap(randn, T, Vmps ⊗ P ← Vmps ⊗ P') x2′ = force_planar(x2) - @tensor contractcheck=true y2[-1 -2; -3 -4] := GL[-1 7; 6] * x2[6 5; 1 3] * O[7 -2; 5 4] * O[4 -4; 3 2] * GR[1 2; -3] - @planar y2′[-1 -2; -3 -4] := GL′[-1 7; 6] * x2′[6 5; 1 3] * O′[7 -2; 5 4] * O′[4 -4; 3 2] * GR′[1 2; -3] + @tensor contractcheck = true y2[-1 -2; -3 -4] := GL[-1 7; 6] * x2[6 5; 1 3] * + O[7 -2; 5 4] * O[4 -4; 3 2] * + GR[1 2; -3] + @planar y2′[-1 -2; -3 -4] := GL′[-1 7; 6] * x2′[6 5; 1 3] * O′[7 -2; 5 4] * + O′[4 -4; 3 2] * GR′[1 2; -3] @test force_planar(y2) ≈ y2′ - + # transfer matrix # ---------------- v = TensorMap(randn, T, Vmps ← Vmps) @@ -107,7 +110,7 @@ end @tensor ρ[-1; -2] := x[-1 2; 1] * conj(x[-2 2; 3]) * v[1; 3] @planar ρ′[-1; -2] := x′[-1 2; 1] * conj(x′[-2 2; 3]) * v′[1; 3] @test force_planar(ρ) ≈ ρ′ - + @tensor ρ2[-1 -2; -3] := GL[1 -2; 3] * x[3 2; -3] * conj(x[1 2; -1]) @plansor ρ3[-1 -2; -3] := GL[1 2; 4] * x[4 5; -3] * τ[2 3; 5 -2] * conj(x[1 3; -1]) @planar ρ2′[-1 -2; -3] := GL′[1 2; 4] * x′[4 5; -3] * τ[2 3; 5 -2] * @@ -115,20 +118,20 @@ end @test force_planar(ρ2) ≈ ρ2′ @test ρ2 ≈ ρ3 end - + @testset "MERA networks" begin Vmera = ℂ^2 - + u = TensorMap(randn, T, Vmera ⊗ Vmera ← Vmera ⊗ Vmera) w = TensorMap(randn, T, Vmera ⊗ Vmera ← Vmera) ρ = TensorMap(randn, T, Vmera ⊗ Vmera ⊗ Vmera ← Vmera ⊗ Vmera ⊗ Vmera) h = TensorMap(randn, T, Vmera ⊗ Vmera ⊗ Vmera ← Vmera ⊗ Vmera ⊗ Vmera) - + u′ = force_planar(u) w′ = force_planar(w) ρ′ = force_planar(ρ) h′ = force_planar(h) - + @tensor begin C = (((((((h[9 3 4; 5 1 2] * u[1 2; 7 12]) * conj(u[3 4; 11 13])) * (u[8 5; 15 6] * w[6 7; 19])) * @@ -138,11 +141,11 @@ end end @planar begin C′ = (((((((h′[9 3 4; 5 1 2] * u′[1 2; 7 12]) * conj(u′[3 4; 11 13])) * - (u′[8 5; 15 6] * w′[6 7; 19])) * - (conj(u′[8 9; 17 10]) * conj(w′[10 11; 22]))) * - ((w′[12 14; 20] * conj(w′[13 14; 23])) * ρ′[18 19 20; 21 22 23])) * - w′[16 15; 18]) * conj(w′[16 17; 21])) + (u′[8 5; 15 6] * w′[6 7; 19])) * + (conj(u′[8 9; 17 10]) * conj(w′[10 11; 22]))) * + ((w′[12 14; 20] * conj(w′[13 14; 23])) * ρ′[18 19 20; 21 22 23])) * + w′[16 15; 18]) * conj(w′[16 17; 21])) end @test C ≈ C′ end -end \ No newline at end of file +end diff --git a/test/sectors.jl b/test/sectors.jl index e957fc38..fbda1e2f 100644 --- a/test/sectors.jl +++ b/test/sectors.jl @@ -115,4 +115,4 @@ println("------------------------------------") end end end -end \ No newline at end of file +end diff --git a/test/spaces.jl b/test/spaces.jl index ed676736..8d02c97f 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -402,4 +402,4 @@ println("------------------------------------") @test @constinferred(hash(W)) == hash(deepcopy(W)) != hash(W') @test W == deepcopy(W) end -end \ No newline at end of file +end diff --git a/test/tensors.jl b/test/tensors.jl index 24401ac7..58fb3d1c 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -78,509 +78,520 @@ for V in spacelist println("---------------------------------------") println("Tensors with symmetry: $Istr") println("---------------------------------------") - @timedtestset "Tensors with symmetry: $Istr" verbose=true begin - V1, V2, V3, V4, V5 = V - @timedtestset "Basic tensor properties" begin - W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 - for T in (Int, Float32, Float64, ComplexF32, ComplexF64, BigFloat) - t = Tensor(zeros, T, W) - @test @constinferred(hash(t)) == hash(deepcopy(t)) - @test scalartype(t) == T - @test norm(t) == 0 - @test codomain(t) == W - @test space(t) == (W ← one(W)) - @test domain(t) == one(W) - @test typeof(t) == @constinferred tensormaptype(spacetype(t), 5, 0, T) - end - end - @timedtestset "Tensor Dict conversion" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - for T in (Int, Float32, ComplexF64) - t = TensorMap(rand, T, W) - d = convert(Dict, t) - @test t == convert(TensorMap, d) + @timedtestset "Tensors with symmetry: $Istr" verbose = true begin + V1, V2, V3, V4, V5 = V + @timedtestset "Basic tensor properties" begin + W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 + for T in (Int, Float32, Float64, ComplexF32, ComplexF64, BigFloat) + t = Tensor(zeros, T, W) + @test @constinferred(hash(t)) == hash(deepcopy(t)) + @test scalartype(t) == T + @test norm(t) == 0 + @test codomain(t) == W + @test space(t) == (W ← one(W)) + @test domain(t) == one(W) + @test typeof(t) == @constinferred tensormaptype(spacetype(t), 5, 0, T) + end end - end - if hasfusiontensor(I) - @timedtestset "Tensor Array conversion" begin + @timedtestset "Tensor Dict conversion" begin W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Int, Float32, ComplexF64) - if T == Int - t = TensorMap(sz -> rand(-20:20, sz), W) - else - t = TensorMap(randn, T, W) - end - a = @constinferred convert(Array, t) - @test t ≈ @constinferred TensorMap(a, W) + t = TensorMap(rand, T, W) + d = convert(Dict, t) + @test t == convert(TensorMap, d) end end - end - @timedtestset "Basic linear algebra" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - for T in (Float32, ComplexF64) - t = TensorMap(rand, T, W) - @test scalartype(t) == T - @test space(t) == W - @test space(t') == W' - @test dim(t) == dim(space(t)) - @test codomain(t) == codomain(W) - @test domain(t) == domain(W) - @test isa(@constinferred(norm(t)), real(T)) - @test norm(t)^2 ≈ dot(t, t) - α = rand(T) - @test norm(α * t) ≈ abs(α) * norm(t) - @test norm(t + t, 2) ≈ 2 * norm(t, 2) - @test norm(t + t, 1) ≈ 2 * norm(t, 1) - @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) - p = 3 * rand(Float64) - @test norm(t + t, p) ≈ 2 * norm(t, p) - @test norm(t) ≈ norm(t') - - t2 = TensorMap(rand, T, W) - β = rand(T) - @test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t2', t')) - @test dot(t2, t) ≈ dot(t', t2') - - i1 = @constinferred(isomorphism(Matrix{T}, V1 ⊗ V2, V2 ⊗ V1)) - i2 = @constinferred(isomorphism(Matrix{T}, V2 ⊗ V1, V1 ⊗ V2)) - @test i1 * i2 == @constinferred(id(Matrix{T}, V1 ⊗ V2)) - @test i2 * i1 == @constinferred(id(Matrix{T}, V2 ⊗ V1)) - - w = @constinferred(isometry(Matrix{T}, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), V1)) - @test dim(w) == 2 * dim(V1 ← V1) - @test w' * w == id(Matrix{T}, V1) - @test w * w' == (w * w')^2 + if hasfusiontensor(I) + @timedtestset "Tensor Array conversion" begin + W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + for T in (Int, Float32, ComplexF64) + if T == Int + t = TensorMap(sz -> rand(-20:20, sz), W) + else + t = TensorMap(randn, T, W) + end + a = @constinferred convert(Array, t) + @test t ≈ @constinferred TensorMap(a, W) + end + end end - end - if hasfusiontensor(I) - @timedtestset "Basic linear algebra: test via conversion" begin + @timedtestset "Basic linear algebra" begin W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Float32, ComplexF64) t = TensorMap(rand, T, W) - t2 = TensorMap(rand, T, W) - @test norm(t, 2) ≈ norm(convert(Array, t), 2) - @test dot(t2, t) ≈ dot(convert(Array, t2), convert(Array, t)) + @test scalartype(t) == T + @test space(t) == W + @test space(t') == W' + @test dim(t) == dim(space(t)) + @test codomain(t) == codomain(W) + @test domain(t) == domain(W) + @test isa(@constinferred(norm(t)), real(T)) + @test norm(t)^2 ≈ dot(t, t) α = rand(T) - @test convert(Array, α * t) ≈ α * convert(Array, t) - @test convert(Array, t + t) ≈ 2 * convert(Array, t) + @test norm(α * t) ≈ abs(α) * norm(t) + @test norm(t + t, 2) ≈ 2 * norm(t, 2) + @test norm(t + t, 1) ≈ 2 * norm(t, 1) + @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) + p = 3 * rand(Float64) + @test norm(t + t, p) ≈ 2 * norm(t, p) + @test norm(t) ≈ norm(t') + + t2 = TensorMap(rand, T, W) + β = rand(T) + @test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t2', t')) + @test dot(t2, t) ≈ dot(t', t2') + + i1 = @constinferred(isomorphism(Matrix{T}, V1 ⊗ V2, V2 ⊗ V1)) + i2 = @constinferred(isomorphism(Matrix{T}, V2 ⊗ V1, V1 ⊗ V2)) + @test i1 * i2 == @constinferred(id(Matrix{T}, V1 ⊗ V2)) + @test i2 * i1 == @constinferred(id(Matrix{T}, V2 ⊗ V1)) + + w = @constinferred(isometry(Matrix{T}, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), + V1)) + @test dim(w) == 2 * dim(V1 ← V1) + @test w' * w == id(Matrix{T}, V1) + @test w * w' == (w * w')^2 end end - @timedtestset "Real and imaginary parts" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64, ComplexF32) - t = TensorMap(randn, T, W, W) - @test real(convert(Array, t)) == convert(Array, @constinferred real(t)) - @test imag(convert(Array, t)) == convert(Array, @constinferred imag(t)) + if hasfusiontensor(I) + @timedtestset "Basic linear algebra: test via conversion" begin + W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + for T in (Float32, ComplexF64) + t = TensorMap(rand, T, W) + t2 = TensorMap(rand, T, W) + @test norm(t, 2) ≈ norm(convert(Array, t), 2) + @test dot(t2, t) ≈ dot(convert(Array, t2), convert(Array, t)) + α = rand(T) + @test convert(Array, α * t) ≈ α * convert(Array, t) + @test convert(Array, t + t) ≈ 2 * convert(Array, t) + end end - end - end - @timedtestset "Tensor conversion" begin - W = V1 ⊗ V2 - t = TensorMap(randn, Float64, W, W) - @test typeof(convert(TensorMap, t')) == typeof(t) - tc = complex(t) - @test convert(typeof(tc), t) == tc - @test typeof(convert(typeof(tc), t)) == typeof(tc) - @test typeof(convert(typeof(tc), t')) == typeof(tc) - end - @timedtestset "Permutations: test via inner product invariance" begin - W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 - t = Tensor(rand, ComplexF64, W) - t′ = Tensor(rand, ComplexF64, W) - for k in 0:5 - for p in permutations(1:5) - p1 = ntuple(n -> p[n], k) - p2 = ntuple(n -> p[k + n], 5 - k) - t2 = @constinferred permute(t, (p1, p2)) - @test norm(t2) ≈ norm(t) - t2′ = permute(t′, (p1, p2)) - @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) + @timedtestset "Real and imaginary parts" begin + W = V1 ⊗ V2 + for T in (Float64, ComplexF64, ComplexF32) + t = TensorMap(randn, T, W, W) + @test real(convert(Array, t)) == convert(Array, @constinferred real(t)) + @test imag(convert(Array, t)) == convert(Array, @constinferred imag(t)) + end end end - end - if hasfusiontensor(I) - @timedtestset "Permutations: test via conversion" begin + @timedtestset "Tensor conversion" begin + W = V1 ⊗ V2 + t = TensorMap(randn, Float64, W, W) + @test typeof(convert(TensorMap, t')) == typeof(t) + tc = complex(t) + @test convert(typeof(tc), t) == tc + @test typeof(convert(typeof(tc), t)) == typeof(tc) + @test typeof(convert(typeof(tc), t')) == typeof(tc) + end + @timedtestset "Permutations: test via inner product invariance" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = Tensor(rand, ComplexF64, W) + t′ = Tensor(rand, ComplexF64, W) for k in 0:5 for p in permutations(1:5) p1 = ntuple(n -> p[n], k) p2 = ntuple(n -> p[k + n], 5 - k) - t2 = permute(t, (p1, p2)) - a2 = convert(Array, t2) - @test a2 ≈ permutedims(convert(Array, t), (p1..., p2...)) - @test convert(Array, transpose(t2)) ≈ permutedims(a2, (5, 4, 3, 2, 1)) + t2 = @constinferred permute(t, (p1, p2)) + @test norm(t2) ≈ norm(t) + t2′ = permute(t′, (p1, p2)) + @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) end end end - end - @timedtestset "Full trace: test self-consistency" begin - t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') - t2 = permute(t, ((1, 2), (4, 3))) - s = @constinferred tr(t2) - @test conj(s) ≈ tr(t2') - if !isdual(V1) - t2 = twist!(t2, 1) + if hasfusiontensor(I) + @timedtestset "Permutations: test via conversion" begin + W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 + t = Tensor(rand, ComplexF64, W) + for k in 0:5 + for p in permutations(1:5) + p1 = ntuple(n -> p[n], k) + p2 = ntuple(n -> p[k + n], 5 - k) + t2 = permute(t, (p1, p2)) + a2 = convert(Array, t2) + @test a2 ≈ permutedims(convert(Array, t), (p1..., p2...)) + @test convert(Array, transpose(t2)) ≈ + permutedims(a2, (5, 4, 3, 2, 1)) + end + end + end end - if isdual(V2) - t2 = twist!(t2, 2) + @timedtestset "Full trace: test self-consistency" begin + t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') + t2 = permute(t, ((1, 2), (4, 3))) + s = @constinferred tr(t2) + @test conj(s) ≈ tr(t2') + if !isdual(V1) + t2 = twist!(t2, 1) + end + if isdual(V2) + t2 = twist!(t2, 2) + end + ss = tr(t2) + @tensor s2 = t[a, b, b, a] + @tensor t3[a, b] := t[a, c, c, b] + @tensor s3 = t3[a, a] + @test ss ≈ s2 + @test ss ≈ s3 end - ss = tr(t2) - @tensor s2 = t[a, b, b, a] - @tensor t3[a, b] := t[a, c, c, b] - @tensor s3 = t3[a, a] - @test ss ≈ s2 - @test ss ≈ s3 - end - @timedtestset "Partial trace: test self-consistency" begin - t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') - @tensor t2[a, b] := t[c, d, b, d, c, a] - @tensor t4[a, b, c, d] := t[d, e, b, e, c, a] - @tensor t5[a, b] := t4[a, b, c, c] - @test t2 ≈ t5 - end - if hasfusiontensor(I) - @timedtestset "Trace: test via conversion" begin + @timedtestset "Partial trace: test self-consistency" begin t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') @tensor t2[a, b] := t[c, d, b, d, c, a] - @tensor t3[a, b] := convert(Array, t)[c, d, b, d, c, a] - @test t3 ≈ convert(Array, t2) + @tensor t4[a, b, c, d] := t[d, e, b, e, c, a] + @tensor t5[a, b] := t4[a, b, c, c] + @test t2 ≈ t5 end - end - @timedtestset "Trace and contraction" begin - t1 = Tensor(rand, ComplexF64, V1 ⊗ V2 ⊗ V3) - t2 = Tensor(rand, ComplexF64, V2' ⊗ V4 ⊗ V1') - t3 = t1 ⊗ t2 - @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] - @tensor tb[a, b] := t3[x, y, a, y, b, x] - @test ta ≈ tb - end - if hasfusiontensor(I) - @timedtestset "Tensor contraction: test via conversion" begin - A1 = TensorMap(randn, ComplexF64, V1' * V2', V3') - A2 = TensorMap(randn, ComplexF64, V3 * V4, V5) - rhoL = TensorMap(randn, ComplexF64, V1, V1) - rhoR = TensorMap(randn, ComplexF64, V5, V5)' # test adjoint tensor - H = TensorMap(randn, ComplexF64, V2 * V4, V2 * V4) - @tensor HrA12[a, s1, s2, c] := rhoL[a, a'] * conj(A1[a', t1, b]) * - A2[b, t2, c'] * rhoR[c', c] * H[s1, s2, t1, t2] + if hasfusiontensor(I) + @timedtestset "Trace: test via conversion" begin + t = Tensor(rand, ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') + @tensor t2[a, b] := t[c, d, b, d, c, a] + @tensor t3[a, b] := convert(Array, t)[c, d, b, d, c, a] + @test t3 ≈ convert(Array, t2) + end + end + @timedtestset "Trace and contraction" begin + t1 = Tensor(rand, ComplexF64, V1 ⊗ V2 ⊗ V3) + t2 = Tensor(rand, ComplexF64, V2' ⊗ V4 ⊗ V1') + t3 = t1 ⊗ t2 + @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] + @tensor tb[a, b] := t3[x, y, a, y, b, x] + @test ta ≈ tb + end + if hasfusiontensor(I) + @timedtestset "Tensor contraction: test via conversion" begin + A1 = TensorMap(randn, ComplexF64, V1' * V2', V3') + A2 = TensorMap(randn, ComplexF64, V3 * V4, V5) + rhoL = TensorMap(randn, ComplexF64, V1, V1) + rhoR = TensorMap(randn, ComplexF64, V5, V5)' # test adjoint tensor + H = TensorMap(randn, ComplexF64, V2 * V4, V2 * V4) + @tensor HrA12[a, s1, s2, c] := rhoL[a, a'] * conj(A1[a', t1, b]) * + A2[b, t2, c'] * rhoR[c', c] * + H[s1, s2, t1, t2] - @tensor HrA12array[a, s1, s2, c] := convert(Array, rhoL)[a, a'] * - conj(convert(Array, A1)[a', t1, b]) * - convert(Array, A2)[b, t2, c'] * - convert(Array, rhoR)[c', c] * - convert(Array, H)[s1, s2, t1, t2] + @tensor HrA12array[a, s1, s2, c] := convert(Array, rhoL)[a, a'] * + conj(convert(Array, A1)[a', t1, b]) * + convert(Array, A2)[b, t2, c'] * + convert(Array, rhoR)[c', c] * + convert(Array, H)[s1, s2, t1, t2] - @test HrA12array ≈ convert(Array, HrA12) - end - end - @timedtestset "Multiplication and inverse: test compatibility" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = V4 ⊗ V5 - for T in (Float64, ComplexF64) - t1 = TensorMap(rand, T, W1, W1) - t2 = TensorMap(rand, T, W2, W2) - t = TensorMap(rand, T, W1, W2) - @test t1 * (t1 \ t) ≈ t - @test (t / t2) * t2 ≈ t - @test t1 \ one(t1) ≈ inv(t1) - @test one(t1) / t1 ≈ pinv(t1) - @test_throws SpaceMismatch inv(t) - @test_throws SpaceMismatch t2 \ t - @test_throws SpaceMismatch t / t1 - tp = pinv(t) * t - @test tp ≈ tp * tp + @test HrA12array ≈ convert(Array, HrA12) + end end - end - if hasfusiontensor(I) - @timedtestset "Multiplication and inverse: test via conversion" begin + @timedtestset "Multiplication and inverse: test compatibility" begin W1 = V1 ⊗ V2 ⊗ V3 W2 = V4 ⊗ V5 - for T in (Float32, Float64, ComplexF32, ComplexF64) + for T in (Float64, ComplexF64) t1 = TensorMap(rand, T, W1, W1) t2 = TensorMap(rand, T, W2, W2) t = TensorMap(rand, T, W1, W2) - d1 = dim(W1) - d2 = dim(W2) - At1 = reshape(convert(Array, t1), d1, d1) - At2 = reshape(convert(Array, t2), d2, d2) - At = reshape(convert(Array, t), d1, d2) - @test reshape(convert(Array, t1 * t), d1, d2) ≈ At1 * At - @test reshape(convert(Array, t1' * t), d1, d2) ≈ At1' * At - @test reshape(convert(Array, t2 * t'), d2, d1) ≈ At2 * At' - @test reshape(convert(Array, t2' * t'), d2, d1) ≈ At2' * At' + @test t1 * (t1 \ t) ≈ t + @test (t / t2) * t2 ≈ t + @test t1 \ one(t1) ≈ inv(t1) + @test one(t1) / t1 ≈ pinv(t1) + @test_throws SpaceMismatch inv(t) + @test_throws SpaceMismatch t2 \ t + @test_throws SpaceMismatch t / t1 + tp = pinv(t) * t + @test tp ≈ tp * tp + end + end + if hasfusiontensor(I) + @timedtestset "Multiplication and inverse: test via conversion" begin + W1 = V1 ⊗ V2 ⊗ V3 + W2 = V4 ⊗ V5 + for T in (Float32, Float64, ComplexF32, ComplexF64) + t1 = TensorMap(rand, T, W1, W1) + t2 = TensorMap(rand, T, W2, W2) + t = TensorMap(rand, T, W1, W2) + d1 = dim(W1) + d2 = dim(W2) + At1 = reshape(convert(Array, t1), d1, d1) + At2 = reshape(convert(Array, t2), d2, d2) + At = reshape(convert(Array, t), d1, d2) + @test reshape(convert(Array, t1 * t), d1, d2) ≈ At1 * At + @test reshape(convert(Array, t1' * t), d1, d2) ≈ At1' * At + @test reshape(convert(Array, t2 * t'), d2, d1) ≈ At2 * At' + @test reshape(convert(Array, t2' * t'), d2, d1) ≈ At2' * At' - @test reshape(convert(Array, inv(t1)), d1, d1) ≈ inv(At1) - @test reshape(convert(Array, pinv(t)), d2, d1) ≈ pinv(At) + @test reshape(convert(Array, inv(t1)), d1, d1) ≈ inv(At1) + @test reshape(convert(Array, pinv(t)), d2, d1) ≈ pinv(At) - if T == Float32 || T == ComplexF32 - continue - end + if T == Float32 || T == ComplexF32 + continue + end - @test reshape(convert(Array, t1 \ t), d1, d2) ≈ At1 \ At - @test reshape(convert(Array, t1' \ t), d1, d2) ≈ At1' \ At - @test reshape(convert(Array, t2 \ t'), d2, d1) ≈ At2 \ At' - @test reshape(convert(Array, t2' \ t'), d2, d1) ≈ At2' \ At' + @test reshape(convert(Array, t1 \ t), d1, d2) ≈ At1 \ At + @test reshape(convert(Array, t1' \ t), d1, d2) ≈ At1' \ At + @test reshape(convert(Array, t2 \ t'), d2, d1) ≈ At2 \ At' + @test reshape(convert(Array, t2' \ t'), d2, d1) ≈ At2' \ At' - @test reshape(convert(Array, t2 / t), d2, d1) ≈ At2 / At - @test reshape(convert(Array, t2' / t), d2, d1) ≈ At2' / At - @test reshape(convert(Array, t1 / t'), d1, d2) ≈ At1 / At' - @test reshape(convert(Array, t1' / t'), d1, d2) ≈ At1' / At' + @test reshape(convert(Array, t2 / t), d2, d1) ≈ At2 / At + @test reshape(convert(Array, t2' / t), d2, d1) ≈ At2' / At + @test reshape(convert(Array, t1 / t'), d1, d2) ≈ At1 / At' + @test reshape(convert(Array, t1' / t'), d1, d2) ≈ At1' / At' + end end end - end - @timedtestset "Factorization" begin - W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 - for T in (Float32, ComplexF64) - # Test both a normal tensor and an adjoint one. - ts = (Tensor(rand, T, W), Tensor(rand, T, W)') - for t in ts - @testset "leftorth with $alg" for alg in - (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) - Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) - QdQ = Q' * Q - @test QdQ ≈ one(QdQ) - @test Q * R ≈ permute(t, ((3, 4, 2), (1, 5))) - if alg isa Polar - @test isposdef(R) - @test domain(R) == codomain(R) == space(t, 1)' ⊗ space(t, 5)' + @timedtestset "Factorization" begin + W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 + for T in (Float32, ComplexF64) + # Test both a normal tensor and an adjoint one. + ts = (Tensor(rand, T, W), Tensor(rand, T, W)') + for t in ts + @testset "leftorth with $alg" for alg in + (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) + QdQ = Q' * Q + @test QdQ ≈ one(QdQ) + @test Q * R ≈ permute(t, ((3, 4, 2), (1, 5))) + if alg isa Polar + @test isposdef(R) + @test domain(R) == codomain(R) == space(t, 1)' ⊗ space(t, 5)' + end end - end - @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), - TensorKit.SDD()) - N = @constinferred leftnull(t, (3, 4, 2), (1, 5); alg=alg) - NdN = N' * N - @test NdN ≈ one(NdN) - @test norm(N' * permute(t, ((3, 4, 2), (1, 5)))) < 100 * eps(norm(t)) - end - @testset "rightorth with $alg" for alg in - (TensorKit.RQ(), TensorKit.RQpos(), - TensorKit.LQ(), TensorKit.LQpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) - L, Q = @constinferred rightorth(t, (3, 4), (2, 1, 5); alg=alg) - QQd = Q * Q' - @test QQd ≈ one(QQd) - @test L * Q ≈ permute(t, ((3, 4), (2, 1, 5))) - if alg isa Polar - @test isposdef(L) - @test domain(L) == codomain(L) == space(t, 3) ⊗ space(t, 4) + @testset "leftnull with $alg" for alg in + (TensorKit.QR(), TensorKit.SVD(), + TensorKit.SDD()) + N = @constinferred leftnull(t, (3, 4, 2), (1, 5); alg=alg) + NdN = N' * N + @test NdN ≈ one(NdN) + @test norm(N' * permute(t, ((3, 4, 2), (1, 5)))) < + 100 * eps(norm(t)) + end + @testset "rightorth with $alg" for alg in + (TensorKit.RQ(), TensorKit.RQpos(), + TensorKit.LQ(), TensorKit.LQpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + L, Q = @constinferred rightorth(t, (3, 4), (2, 1, 5); alg=alg) + QQd = Q * Q' + @test QQd ≈ one(QQd) + @test L * Q ≈ permute(t, ((3, 4), (2, 1, 5))) + if alg isa Polar + @test isposdef(L) + @test domain(L) == codomain(L) == space(t, 3) ⊗ space(t, 4) + end + end + @testset "rightnull with $alg" for alg in + (TensorKit.LQ(), TensorKit.SVD(), + TensorKit.SDD()) + M = @constinferred rightnull(t, (3, 4), (2, 1, 5); alg=alg) + MMd = M * M' + @test MMd ≈ one(MMd) + @test norm(permute(t, ((3, 4), (2, 1, 5))) * M') < + 100 * eps(norm(t)) + end + @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) + U, S, V = @constinferred tsvd(t, (3, 4, 2), (1, 5); alg=alg) + UdU = U' * U + @test UdU ≈ one(UdU) + VVd = V * V' + @test VVd ≈ one(VVd) + @test U * S * V ≈ permute(t, ((3, 4, 2), (1, 5))) end end - @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), - TensorKit.SDD()) - M = @constinferred rightnull(t, (3, 4), (2, 1, 5); alg=alg) - MMd = M * M' - @test MMd ≈ one(MMd) - @test norm(permute(t, ((3, 4), (2, 1, 5))) * M') < 100 * eps(norm(t)) - end - @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) - U, S, V = @constinferred tsvd(t, (3, 4, 2), (1, 5); alg=alg) - UdU = U' * U - @test UdU ≈ one(UdU) - VVd = V * V' - @test VVd ≈ one(VVd) - @test U * S * V ≈ permute(t, ((3, 4, 2), (1, 5))) - end - end - @testset "empty tensor" begin - t = TensorMap(randn, T, V1 ⊗ V2, typeof(V1)()) - @testset "leftorth with $alg" for alg in - (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) - Q, R = @constinferred leftorth(t; alg=alg) - @test Q == t - @test dim(Q) == dim(R) == 0 - end - @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), - TensorKit.SDD()) - N = @constinferred leftnull(t; alg=alg) - @test N' * N ≈ id(domain(N)) - @test N * N' ≈ id(codomain(N)) - end - @testset "rightorth with $alg" for alg in - (TensorKit.RQ(), TensorKit.RQpos(), - TensorKit.LQ(), TensorKit.LQpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) - L, Q = @constinferred rightorth(copy(t'); alg=alg) - @test Q == t' - @test dim(Q) == dim(L) == 0 - end - @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), - TensorKit.SDD()) - M = @constinferred rightnull(copy(t'); alg=alg) - @test M * M' ≈ id(codomain(M)) - @test M' * M ≈ id(domain(M)) - end - @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) - U, S, V = @constinferred tsvd(t; alg=alg) - @test U == t - @test dim(U) == dim(S) == dim(V) + @testset "empty tensor" begin + t = TensorMap(randn, T, V1 ⊗ V2, typeof(V1)()) + @testset "leftorth with $alg" for alg in + (TensorKit.QR(), TensorKit.QRpos(), + TensorKit.QL(), TensorKit.QLpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + Q, R = @constinferred leftorth(t; alg=alg) + @test Q == t + @test dim(Q) == dim(R) == 0 + end + @testset "leftnull with $alg" for alg in + (TensorKit.QR(), TensorKit.SVD(), + TensorKit.SDD()) + N = @constinferred leftnull(t; alg=alg) + @test N' * N ≈ id(domain(N)) + @test N * N' ≈ id(codomain(N)) + end + @testset "rightorth with $alg" for alg in + (TensorKit.RQ(), TensorKit.RQpos(), + TensorKit.LQ(), TensorKit.LQpos(), + TensorKit.Polar(), TensorKit.SVD(), + TensorKit.SDD()) + L, Q = @constinferred rightorth(copy(t'); alg=alg) + @test Q == t' + @test dim(Q) == dim(L) == 0 + end + @testset "rightnull with $alg" for alg in + (TensorKit.LQ(), TensorKit.SVD(), + TensorKit.SDD()) + M = @constinferred rightnull(copy(t'); alg=alg) + @test M * M' ≈ id(codomain(M)) + @test M' * M ≈ id(domain(M)) + end + @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) + U, S, V = @constinferred tsvd(t; alg=alg) + @test U == t + @test dim(U) == dim(S) == dim(V) + end end - end - t = Tensor(rand, T, V1 ⊗ V1' ⊗ V2 ⊗ V2') - @testset "eig and isposdef" begin - D, V = eigen(t, (1, 3), (2, 4)) - D̃, Ṽ = @constinferred eig(t, (1, 3), (2, 4)) - @test D ≈ D̃ - @test V ≈ Ṽ - VdV = V' * V - VdV = (VdV + VdV') / 2 - @test isposdef(VdV) - t2 = permute(t, ((1, 3), (2, 4))) - @test t2 * V ≈ V * D - @test !isposdef(t2) # unlikely for non-hermitian map - t2 = (t2 + t2') - D, V = eigen(t2) - VdV = V' * V - @test VdV ≈ one(VdV) - D̃, Ṽ = @constinferred eigh(t2) - @test D ≈ D̃ - @test V ≈ Ṽ - λ = minimum(minimum(real(LinearAlgebra.diag(b))) for (c, b) in blocks(D)) - @test isposdef(t2) == isposdef(λ) - @test isposdef(t2 - λ * one(t2) + 0.1 * one(t2)) - @test !isposdef(t2 - λ * one(t2) - 0.1 * one(t2)) + t = Tensor(rand, T, V1 ⊗ V1' ⊗ V2 ⊗ V2') + @testset "eig and isposdef" begin + D, V = eigen(t, (1, 3), (2, 4)) + D̃, Ṽ = @constinferred eig(t, (1, 3), (2, 4)) + @test D ≈ D̃ + @test V ≈ Ṽ + VdV = V' * V + VdV = (VdV + VdV') / 2 + @test isposdef(VdV) + t2 = permute(t, ((1, 3), (2, 4))) + @test t2 * V ≈ V * D + @test !isposdef(t2) # unlikely for non-hermitian map + t2 = (t2 + t2') + D, V = eigen(t2) + VdV = V' * V + @test VdV ≈ one(VdV) + D̃, Ṽ = @constinferred eigh(t2) + @test D ≈ D̃ + @test V ≈ Ṽ + λ = minimum(minimum(real(LinearAlgebra.diag(b))) + for (c, b) in blocks(D)) + @test isposdef(t2) == isposdef(λ) + @test isposdef(t2 - λ * one(t2) + 0.1 * one(t2)) + @test !isposdef(t2 - λ * one(t2) - 0.1 * one(t2)) + end end end - end - @timedtestset "Tensor truncation" begin - for T in (Float32, ComplexF64) - for p in (1, 2, 3, Inf) - # Test both a normal tensor and an adjoint one. - ts = (TensorMap(randn, T, V1 ⊗ V2 ⊗ V3, V4 ⊗ V5), - TensorMap(randn, T, V4 ⊗ V5, V1 ⊗ V2 ⊗ V3)') - for t in ts - U₀, S₀, V₀, = tsvd(t) - t = rmul!(t, 1 / norm(S₀, p)) - U, S, V, ϵ = @constinferred tsvd(t; trunc=truncerr(5e-1), p=p) - # @show p, ϵ - # @show domain(S) - # @test min(space(S,1), space(S₀,1)) != space(S₀,1) - U′, S′, V′, ϵ′ = tsvd(t; trunc=truncerr(nextfloat(ϵ)), p=p) - @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) - U′, S′, V′, ϵ′ = tsvd(t; trunc=truncdim(ceil(Int, dim(domain(S)))), p=p) - @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) - U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) - @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) - # results with truncationcutoff cannot be compared because they don't take degeneracy into account, and thus truncate differently - U, S, V, ϵ = tsvd(t; trunc=truncbelow(1 / dim(domain(S₀))), p=p) - # @show p, ϵ - # @show domain(S) - # @test min(space(S,1), space(S₀,1)) != space(S₀,1) - U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) - @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) + @timedtestset "Tensor truncation" begin + for T in (Float32, ComplexF64) + for p in (1, 2, 3, Inf) + # Test both a normal tensor and an adjoint one. + ts = (TensorMap(randn, T, V1 ⊗ V2 ⊗ V3, V4 ⊗ V5), + TensorMap(randn, T, V4 ⊗ V5, V1 ⊗ V2 ⊗ V3)') + for t in ts + U₀, S₀, V₀, = tsvd(t) + t = rmul!(t, 1 / norm(S₀, p)) + U, S, V, ϵ = @constinferred tsvd(t; trunc=truncerr(5e-1), p=p) + # @show p, ϵ + # @show domain(S) + # @test min(space(S,1), space(S₀,1)) != space(S₀,1) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncerr(nextfloat(ϵ)), p=p) + @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncdim(ceil(Int, dim(domain(S)))), + p=p) + @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) + @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) + # results with truncationcutoff cannot be compared because they don't take degeneracy into account, and thus truncate differently + U, S, V, ϵ = tsvd(t; trunc=truncbelow(1 / dim(domain(S₀))), p=p) + # @show p, ϵ + # @show domain(S) + # @test min(space(S,1), space(S₀,1)) != space(S₀,1) + U′, S′, V′, ϵ′ = tsvd(t; trunc=truncspace(space(S, 1)), p=p) + @test (U, S, V, ϵ) == (U′, S′, V′, ϵ′) + end end end end - end - if hasfusiontensor(I) - @timedtestset "Tensor functions" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64) - t = TensorMap(randn, T, W, W) - s = dim(W) - expt = @constinferred exp(t) - @test reshape(convert(Array, expt), (s, s)) ≈ - exp(reshape(convert(Array, t), (s, s))) + if hasfusiontensor(I) + @timedtestset "Tensor functions" begin + W = V1 ⊗ V2 + for T in (Float64, ComplexF64) + t = TensorMap(randn, T, W, W) + s = dim(W) + expt = @constinferred exp(t) + @test reshape(convert(Array, expt), (s, s)) ≈ + exp(reshape(convert(Array, t), (s, s))) - @test (@constinferred sqrt(t))^2 ≈ t - @test reshape(convert(Array, sqrt(t^2)), (s, s)) ≈ - sqrt(reshape(convert(Array, t^2), (s, s))) + @test (@constinferred sqrt(t))^2 ≈ t + @test reshape(convert(Array, sqrt(t^2)), (s, s)) ≈ + sqrt(reshape(convert(Array, t^2), (s, s))) - @test exp(@constinferred log(expt)) ≈ expt - @test reshape(convert(Array, log(expt)), (s, s)) ≈ - log(reshape(convert(Array, expt), (s, s))) + @test exp(@constinferred log(expt)) ≈ expt + @test reshape(convert(Array, log(expt)), (s, s)) ≈ + log(reshape(convert(Array, expt), (s, s))) - @test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ id(W) - @test (@constinferred tan(t)) ≈ sin(t) / cos(t) - @test (@constinferred cot(t)) ≈ cos(t) / sin(t) - @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ id(W) - @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) - @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t) + @test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ id(W) + @test (@constinferred tan(t)) ≈ sin(t) / cos(t) + @test (@constinferred cot(t)) ≈ cos(t) / sin(t) + @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ id(W) + @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) + @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t) - t1 = sin(t) - @test sin(@constinferred asin(t1)) ≈ t1 - t2 = cos(t) - @test cos(@constinferred acos(t2)) ≈ t2 - t3 = sinh(t) - @test sinh(@constinferred asinh(t3)) ≈ t3 - t4 = cosh(t) - @test cosh(@constinferred acosh(t4)) ≈ t4 - t5 = tan(t) - @test tan(@constinferred atan(t5)) ≈ t5 - t6 = cot(t) - @test cot(@constinferred acot(t6)) ≈ t6 - t7 = tanh(t) - @test tanh(@constinferred atanh(t7)) ≈ t7 - t8 = coth(t) - @test coth(@constinferred acoth(t8)) ≈ t8 + t1 = sin(t) + @test sin(@constinferred asin(t1)) ≈ t1 + t2 = cos(t) + @test cos(@constinferred acos(t2)) ≈ t2 + t3 = sinh(t) + @test sinh(@constinferred asinh(t3)) ≈ t3 + t4 = cosh(t) + @test cosh(@constinferred acosh(t4)) ≈ t4 + t5 = tan(t) + @test tan(@constinferred atan(t5)) ≈ t5 + t6 = cot(t) + @test cot(@constinferred acot(t6)) ≈ t6 + t7 = tanh(t) + @test tanh(@constinferred atanh(t7)) ≈ t7 + t8 = coth(t) + @test coth(@constinferred acoth(t8)) ≈ t8 + end end end - end - @timedtestset "Sylvester equation" begin - for T in (Float32, ComplexF64) - tA = TensorMap(rand, T, V1 ⊗ V3, V1 ⊗ V3) - tB = TensorMap(rand, T, V2 ⊗ V4, V2 ⊗ V4) - tA = 3 // 2 * leftorth(tA; alg=Polar())[1] - tB = 1 // 5 * leftorth(tB; alg=Polar())[1] - tC = TensorMap(rand, T, V1 ⊗ V3, V2 ⊗ V4) - t = @constinferred sylvester(tA, tB, tC) - @test codomain(t) == V1 ⊗ V3 - @test domain(t) == V2 ⊗ V4 - @test norm(tA * t + t * tB + tC) < - (norm(tA) + norm(tB) + norm(tC)) * eps(real(T))^(2 / 3) - if hasfusiontensor(I) - matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) - @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) + @timedtestset "Sylvester equation" begin + for T in (Float32, ComplexF64) + tA = TensorMap(rand, T, V1 ⊗ V3, V1 ⊗ V3) + tB = TensorMap(rand, T, V2 ⊗ V4, V2 ⊗ V4) + tA = 3 // 2 * leftorth(tA; alg=Polar())[1] + tB = 1 // 5 * leftorth(tB; alg=Polar())[1] + tC = TensorMap(rand, T, V1 ⊗ V3, V2 ⊗ V4) + t = @constinferred sylvester(tA, tB, tC) + @test codomain(t) == V1 ⊗ V3 + @test domain(t) == V2 ⊗ V4 + @test norm(tA * t + t * tB + tC) < + (norm(tA) + norm(tB) + norm(tC)) * eps(real(T))^(2 / 3) + if hasfusiontensor(I) + matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) + @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) + end end end - end - @timedtestset "Tensor product: test via norm preservation" begin - for T in (Float32, ComplexF64) - t1 = TensorMap(rand, T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) - t2 = TensorMap(rand, T, V2 ⊗ V1 ⊗ V3, V1 ⊗ V1) - t = @constinferred (t1 ⊗ t2) - @test norm(t) ≈ norm(t1) * norm(t2) - end - end - if hasfusiontensor(I) - @timedtestset "Tensor product: test via conversion" begin + @timedtestset "Tensor product: test via norm preservation" begin for T in (Float32, ComplexF64) - t1 = TensorMap(rand, T, V2 ⊗ V3 ⊗ V1, V1) - t2 = TensorMap(rand, T, V2 ⊗ V1 ⊗ V3, V2) + t1 = TensorMap(rand, T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) + t2 = TensorMap(rand, T, V2 ⊗ V1 ⊗ V3, V1 ⊗ V1) t = @constinferred (t1 ⊗ t2) - d1 = dim(codomain(t1)) - d2 = dim(codomain(t2)) - d3 = dim(domain(t1)) - d4 = dim(domain(t2)) - At = convert(Array, t) - @test reshape(At, (d1, d2, d3, d4)) ≈ - reshape(convert(Array, t1), (d1, 1, d3, 1)) .* - reshape(convert(Array, t2), (1, d2, 1, d4)) + @test norm(t) ≈ norm(t1) * norm(t2) end end - end - @timedtestset "Tensor product: test via tensor contraction" begin - for T in (Float32, ComplexF64) - t1 = Tensor(rand, T, V2 ⊗ V3 ⊗ V1) - t2 = Tensor(rand, T, V2 ⊗ V1 ⊗ V3) - t = @constinferred (t1 ⊗ t2) - @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] - @test t ≈ t′ + if hasfusiontensor(I) + @timedtestset "Tensor product: test via conversion" begin + for T in (Float32, ComplexF64) + t1 = TensorMap(rand, T, V2 ⊗ V3 ⊗ V1, V1) + t2 = TensorMap(rand, T, V2 ⊗ V1 ⊗ V3, V2) + t = @constinferred (t1 ⊗ t2) + d1 = dim(codomain(t1)) + d2 = dim(codomain(t2)) + d3 = dim(domain(t1)) + d4 = dim(domain(t2)) + At = convert(Array, t) + @test reshape(At, (d1, d2, d3, d4)) ≈ + reshape(convert(Array, t1), (d1, 1, d3, 1)) .* + reshape(convert(Array, t2), (1, d2, 1, d4)) + end + end + end + @timedtestset "Tensor product: test via tensor contraction" begin + for T in (Float32, ComplexF64) + t1 = Tensor(rand, T, V2 ⊗ V3 ⊗ V1) + t2 = Tensor(rand, T, V2 ⊗ V1 ⊗ V3) + t = @constinferred (t1 ⊗ t2) + @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] + @test t ≈ t′ + end end end end -end @timedtestset "Deligne tensor product: test via conversion" begin @testset for Vlist1 in (Vtr, VSU₂), Vlist2 in (Vtr, Vℤ₂) From b50abc1c7e21218231b0183d82bfe0e6550e100a Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Sun, 13 Aug 2023 14:48:46 +0200 Subject: [PATCH 35/57] Bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 92a497d8..d0b13861 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TensorKit" uuid = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" authors = ["Jutho Haegeman"] -version = "0.10.1" +version = "0.11.0" [deps] HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" From 29d4ef77072845dd2f19acac306bc4c3dad5556d Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:22:50 +0200 Subject: [PATCH 36/57] Add compat VectorInterface --- Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d0b13861..8429a9ae 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,8 @@ Strided = "2" TensorOperations = "4.0.2" TupleTools = "1.1" WignerSymbols = "1,2" -julia = "1.6 - 1" +julia = "1.6" +VectorInterface = "0.2" [extras] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" From 7703e4b47f9c84dce48dd71d2d4da58fae003da9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 14 Aug 2023 16:39:03 +0200 Subject: [PATCH 37/57] Bugfix non-abelian add_transform! Original version had a typo only affecting non-abelian groups with more than 1 threads Threads were not being used anyways so disabled them and added TODO to fix this --- src/tensors/indexmanipulations.jl | 34 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 19208fdd..7e80f938 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -326,18 +326,32 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen elseif β != 1 tdst = scale!(tdst, β) end - if Threads.nthreads() > 1 - Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) - _add_sector!(tdst, tsrc, fusiontreemap, s₁, s₂, α, β, backend...) - end - 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...) - end + + # TODO: implement multithreading for general symmetries + # Currently disable multithreading for general symmetries, requires more testing and + # possibly a different approach. Ideally, we'd loop over output blocks in parallel, to + # avoid parallel writing, but this requires the inverse of the fusiontreetransform. + + 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...) end end + + # if Threads.nthreads() > 1 + # Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) + # _add_sectors!(tdst, tsrc, fusiontreemap, s₁, s₂, α, β, backend...) + # end + # 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...) + # end + # end + # end + return nothing end From ea38b4ba852f6afb602dea54e43912cf8c33970e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 14 Aug 2023 16:48:12 +0200 Subject: [PATCH 38/57] Bump hotfix version 0.11.1 --- Project.toml | 2 +- src/tensors/indexmanipulations.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index 8429a9ae..c4c11529 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TensorKit" uuid = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" authors = ["Jutho Haegeman"] -version = "0.11.0" +version = "0.11.1" [deps] HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 7e80f938..7e3004ce 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -326,19 +326,19 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen elseif β != 1 tdst = scale!(tdst, β) end - + # TODO: implement multithreading for general symmetries # Currently disable multithreading for general symmetries, requires more testing and # possibly a different approach. Ideally, we'd loop over output blocks in parallel, to # avoid parallel writing, but this requires the inverse of the fusiontreetransform. - + 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...) end end - + # if Threads.nthreads() > 1 # Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) # _add_sectors!(tdst, tsrc, fusiontreemap, s₁, s₂, α, β, backend...) @@ -351,7 +351,7 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen # end # end # end - + return nothing end From 0ef2b369a0e4ea13817277de1e206704d0f74ab1 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 19 Aug 2023 20:05:33 +0200 Subject: [PATCH 39/57] Fix argument order `braid(!)` Somehow the arguments got mangled. There is still a difference between the fusiontrees and the tensormaps We should probably consider changing this. --- src/tensors/indexmanipulations.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 7e3004ce..dedffe05 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -121,7 +121,7 @@ function braid(t::TensorMap{S}, (p₁, p₂)::Index2Tuple, levels::IndexTuple; cod = ProductSpace{S}(map(n -> space(t, n), p₁)) dom = ProductSpace{S}(map(n -> dual(space(t, n)), p₂)) @inbounds begin - return braid!(similar(t, cod ← dom), t, levels, (p₁, p₂)) + return braid!(similar(t, cod ← dom), t, (p₁, p₂), levels) end end # TODO: braid for `AdjointTensorMap`; think about how to map the `levels` argument. @@ -250,7 +250,8 @@ end levels1 = TupleTools.getindices(levels, codomainind(tsrc)) levels2 = TupleTools.getindices(levels, domainind(tsrc)) - treebraider(f₁, f₂) = braid(f₁, f₂, p[1], levels1, levels2, p[2]) + # TODO: arg order for tensormaps is different than for fusiontrees + treebraider(f₁, f₂) = braid(f₁, f₂, levels1, levels2, p...) return add_transform!(tdst, tsrc, p, treebraider, α, β, backend...) end From be4ed191663ad5f22c50f19eb837fa6842cebad2 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 19 Aug 2023 23:20:37 +0200 Subject: [PATCH 40/57] Reenable braidingtensor specializations --- src/fusiontrees/manipulations.jl | 12 +- src/tensors/braidingtensor.jl | 218 ++++++++++++++++--------------- 2 files changed, 120 insertions(+), 110 deletions(-) diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index a2acbab8..549d3e0a 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -795,7 +795,7 @@ function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} end BraidingStyle(I) isa NoBraiding && - throw(SectorMismatch("Cannot braid sector " * type_repr(I))) + throw(SectorMismatch("Cannot braid sectors $(uncoupled[i]) and $(uncoupled[i + 1])")) if i == 1 c = N > 2 ? inner[1] : coupled′ @@ -927,7 +927,11 @@ function braid(f::FusionTree{I,N}, f′ = FusionTree{I}(uncoupled′, coupled′, isdual′) return fusiontreedict(I)(f′ => coeff) else - coeff = Rsymbol(one(I), one(I), one(I))[1, 1] + u = one(I) + T = BraidingStyle(I) isa NoBraiding ? + typeof(Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) : + typeof(Rsymbol(u, u, u)[1, 1] * Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) + coeff = one(T) trees = FusionTreeDict(f => coeff) newtrees = empty(trees) for s in permutation2swaps(p) @@ -1004,7 +1008,9 @@ function braid(f₁::FusionTree{I}, f₂::FusionTree{I}, else if usebraidcache_nonabelian[] u = one(I) - T = typeof(sqrtdim(u) * Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1] * + T = BraidingStyle(I) isa NoBraiding ? + typeof(Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1]) : + typeof(sqrtdim(u) * Fsymbol(u, u, u, u, u, u)[1, 1, 1, 1] * Rsymbol(u, u, u)[1, 1]) F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 54d933c9..bd19bf41 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -179,107 +179,114 @@ end blocks(b::BraidingTensor) = blocks(TensorMap(b)) -function planar_contract!(C::AbstractTensorMap{S}, - A::BraidingTensor{S}, - (oindA, cindA)::Index2Tuple{2,2}, - B::AbstractTensorMap{S}, - (cindB, oindB)::Index2Tuple{2,<:Any}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{2,2}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{2,N₃}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backend::Backend...) where {S,N₁,N₂,N₃} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' + if space(B, cindB[1]) != space(A, cindA[1])' || space(B, cindB[2]) != space(A, cindA[2]) + throw(SpaceMismatch()) + end if BraidingStyle(sectortype(B)) isa Bosonic - return add!(α, B, β, C, reverse(cindB), oindB) + return add_permute!(C, B, (reverse(cindB), oindB), α, β, backend...) end - if iszero(β) - fill!(C, β) - elseif β != 1 - rmul!(C, β) - end - braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) - inv_braid = braidingtensor_levels[cindA[1]] > braidingtensor_levels[cindA[2]] - for (f₁, f₂) in fusiontrees(B) - local newtrees - for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) - for (f₁′′, coeff′′) in artin_braid(f₁′, 1; inv=inv_braid) - f12 = (f₁′′, f₂′) - coeff = coeff′ * coeff′′ - if @isdefined newtrees - newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff - else - newtrees = Dict(f12 => coeff) - end - end - end - for ((f₁′, f₂′), coeff) in newtrees - TO._add!(coeff * α, B[f₁, f₂], true, C[f₁′, f₂′], (reverse(cindB)..., oindB...)) - end - end - return C + τ_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) + levels = (τ_levels[cindA[1]], τ_levels[cindA[2]], ntuple(i -> 0, N₃)...) + return add_braid!(C, B, (reverse(cindB), oindB), levels, α, β, backend...) + + # inv_braid = braidingtensor_levels[cindA[1]] > braidingtensor_levels[cindA[2]] + # for (f₁, f₂) in fusiontrees(B) + # local newtrees + # for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, cindB, oindB) + # for (f₁′′, coeff′′) in artin_braid(f₁′, 1; inv=inv_braid) + # f12 = (f₁′′, f₂′) + # coeff = coeff′ * coeff′′ + # if @isdefined newtrees + # newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff + # else + # newtrees = Dict(f12 => coeff) + # end + # end + # end + # for ((f₁′, f₂′), coeff) in newtrees + # TO._add!(coeff * α, B[f₁, f₂], true, C[f₁′, f₂′], (reverse(cindB)..., oindB...)) + # end + # end + # return C end -function planar_contract!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, - (oindA, cindA)::Index2Tuple{<:Any,2}, - B::BraidingTensor{S}, - (cindB, oindB)::Index2Tuple{2,2}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{N₃,2}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{2,2}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backend::Backend...) where {S,N₁,N₂,N₃} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - @assert space(B, cindB[1]) == space(A, cindA[1])' && - space(B, cindB[2]) == space(A, cindA[2])' + if space(B, cindB[1]) != space(A, cindA[1])' || + space(B, cindB[2]) != space(A, cindA[2])' + # @show space(B, cindB[1]), space(A, cindA[1]) + # @show space(B, cindB[2]), space(A, cindA[2]) + throw(SpaceMismatch("$(space(C)) ≠ permute($(space(A))[$oindA, $cindA] * $(space(B))[$cindB, $oindB], ($p1, $p2)")) + end if BraidingStyle(sectortype(A)) isa Bosonic - return add!(α, A, β, C, oindA, reverse(cindA)) + return add_permute!(C, A, (oindA, reverse(cindA)), α, β, backend...) end - if iszero(β) - fill!(C, β) - elseif β != 1 - rmul!(C, β) - end - braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) - inv_braid = braidingtensor_levels[cindB[1]] > braidingtensor_levels[cindB[2]] - for (f₁, f₂) in fusiontrees(A) - local newtrees - for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) - for (f₂′′, coeff′′) in artin_braid(f₂′, 1; inv=inv_braid) - f12 = (f₁′, f₂′′) - coeff = coeff′ * conj(coeff′′) - if @isdefined newtrees - newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff - else - newtrees = Dict(f12 => coeff) - end - end - end - for ((f₁′, f₂′), coeff) in newtrees - TO._add!(coeff * α, A[f₁, f₂], true, C[f₁′, f₂′], (oindA..., reverse(cindA)...)) - end - end - return C + τ_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) + levels = (ntuple(i -> 0, N₃)..., τ_levels[cindB[1]], τ_levels[cindB[2]]) + return add_braid!(C, A, (oindA, reverse(cindA)), levels, α, β, backend...) + + # if iszero(β) + # fill!(C, β) + # elseif β != 1 + # rmul!(C, β) + # end + # braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) + # inv_braid = braidingtensor_levels[cindB[1]] > braidingtensor_levels[cindB[2]] + # for (f₁, f₂) in fusiontrees(A) + # local newtrees + # for ((f₁′, f₂′), coeff′) in transpose(f₁, f₂, oindA, cindA) + # for (f₂′′, coeff′′) in artin_braid(f₂′, 1; inv=inv_braid) + # f12 = (f₁′, f₂′′) + # coeff = coeff′ * conj(coeff′′) + # if @isdefined newtrees + # newtrees[f12] = get(newtrees, f12, zero(coeff)) + coeff + # else + # newtrees = Dict(f12 => coeff) + # end + # end + # end + # for ((f₁′, f₂′), coeff) in newtrees + # TO._add!(coeff * α, A[f₁, f₂], true, C[f₁′, f₂′], (oindA..., reverse(cindA)...)) + # end + # end + # return C end -function planar_contract!(C::AbstractTensorMap{S}, - A::BraidingTensor{S}, - (oindA, cindA)::Index2Tuple{0,4}, - B::AbstractTensorMap{S}, - (cindB, oindB)::Index2Tuple{4,<:Any}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{0,4}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{4,<:Any}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backend::Backend...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, @@ -340,15 +347,14 @@ function planar_contract!(C::AbstractTensorMap{S}, end return C end - -function planar_contract!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, - (oindA, cindA)::Index2Tuple{0,4}, - B::BraidingTensor{S}, - (cindB, oindB)::Index2Tuple{4,<:Any}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{0,4}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{4,<:Any}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backends...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, @@ -409,15 +415,14 @@ function planar_contract!(C::AbstractTensorMap{S}, end return C end - -function planar_contract!(C::AbstractTensorMap{S}, - A::BraidingTensor{S}, - (oindA, cindA)::Index2Tuple{1,3}, - B::AbstractTensorMap{S}, - (cindB, oindB)::Index2Tuple{1,<:Any}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::BraidingTensor{S}, + (oindA, cindA)::Index2Tuple{1,3}, + B::AbstractTensorMap{S}, + (cindB, oindB)::Index2Tuple{1,<:Any}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backend::Backend...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, @@ -472,15 +477,14 @@ function planar_contract!(C::AbstractTensorMap{S}, end return C end - -function planar_contract!(C::AbstractTensorMap{S}, - A::AbstractTensorMap{S}, - (oindA, cindA)::Index2Tuple{<:Any,3}, - B::BraidingTensor{S}, - (cindB, oindB)::Index2Tuple{3,1}, - (p1, p2)::Index2Tuple, - α::Number, β::Number, - backends...) where {S} +function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, + A::AbstractTensorMap{S}, + (oindA, cindA)::Index2Tuple{<:Any,3}, + B::BraidingTensor{S}, + (cindB, oindB)::Index2Tuple{3,1}, + (p1, p2)::Index2Tuple{N₁,N₂}, + α::Number, β::Number, + backend::Backend...) where {S,N₁,N₂} codA, domA = codomainind(A), domainind(A) codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, From c4f13b21fafa1f6a99b47d4c4be034e619e98917 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 20 Aug 2023 12:09:34 +0200 Subject: [PATCH 41/57] Improve TensorMap show The show method for TensorMap will now call show of HomSpace. This ensures that `TensorMap{S,N,1}` and `TensorMap{S,1,N}` will no longer print things like: `TensorMap(ProductSpace(...) <- ProductSpace(...))` and instead omit the ProductSpace. --- src/tensors/tensor.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 5a828327..550ccd07 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -503,14 +503,14 @@ end # Show #------ function Base.summary(t::TensorMap) - return print("TensorMap(", codomain(t), " ← ", domain(t), ")") + return print("TensorMap(", space(t), ")") end function Base.show(io::IO, t::TensorMap{S}) where {S<:IndexSpace} if get(io, :compact, false) - print(io, "TensorMap(", codomain(t), " ← ", domain(t), ")") + print(io, "TensorMap(", space(t), ")") return end - println(io, "TensorMap(", codomain(t), " ← ", domain(t), "):") + println(io, "TensorMap(", space(t), "):") if sectortype(S) == Trivial Base.print_array(io, t[]) println(io) From 8b53f73bcd7da8b4bf247d6b01a3f76f016e0f7c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 24 Aug 2023 17:58:29 +0200 Subject: [PATCH 42/57] Add promotion rule for `TensorMap`s --- src/tensors/tensor.jl | 6 ++++++ test/tensors.jl | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 550ccd07..5c815867 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -573,3 +573,9 @@ function Base.convert(T::Type{TensorMap{S,N₁,N₂,I,A,F₁,F₂}}, return TensorMap(data, codomain(t), domain(t)) end end + +function Base.promote_rule(::Type{<:T1}, + t2::Type{<:T2}) where {S,N₁,N₂,T1<:TensorMap{S,N₁,N₂}, + T2<:TensorMap{S,N₁,N₂}} + return tensormaptype(S, N₁, N₂, promote_type(storagetype(T1), storagetype(T2))) +end diff --git a/test/tensors.jl b/test/tensors.jl index 58fb3d1c..0a1d8edf 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -185,6 +185,8 @@ for V in spacelist @test convert(typeof(tc), t) == tc @test typeof(convert(typeof(tc), t)) == typeof(tc) @test typeof(convert(typeof(tc), t')) == typeof(tc) + @test Base.promote_typeof(t, tc) == typeof(tc) + @test Base.promote_typeof(tc, t) == typeof(tc + t) end @timedtestset "Permutations: test via inner product invariance" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 From e1bad6863e1b667dba2d077f23f9ee826d98106b Mon Sep 17 00:00:00 2001 From: Jutho Date: Wed, 30 Aug 2023 01:16:37 +0200 Subject: [PATCH 43/57] fix nonabelian multithreaded add --- src/tensors/indexmanipulations.jl | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index dedffe05..6b91145b 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -327,16 +327,17 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen elseif β != 1 tdst = scale!(tdst, β) end - - # TODO: implement multithreading for general symmetries - # Currently disable multithreading for general symmetries, requires more testing and - # possibly a different approach. Ideally, we'd loop over output blocks in parallel, to - # avoid parallel writing, but this requires the inverse of the fusiontreetransform. - - 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...) + if Threads.nthreads() > 1 + Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) + Threads.@spawn _add_nonabelian_sector!(tdst, tsrc, p, fusiontreetransform, s₁, + s₂, α, β, backend...) + end + 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...) + end end end @@ -356,9 +357,10 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen return nothing end -function _add_sectors!(tdst, tsrc, p, fusiontreetransform, s₁, s₂, α, β, backend...) +function _add_nonabelian_sector!(tdst, tsrc, p, fusiontreetransform, s₁, s₂, α, β, + backend...) for (f₁, f₂) in fusiontrees(tsrc) - (f₁.outgoing == s₁ && f₂.outgoing == s₂) || continue + (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...) end From 016fb8bb166d717de083f6aa36f2426582d8f1be Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 30 Aug 2023 10:32:30 +0200 Subject: [PATCH 44/57] Update Readme to also refer to other packages --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 26ce9c02..04e2643b 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,7 @@ A Julia package for large-scale tensor computations, with a hint of category the Install via the package manager. Check out the [tutorial](https://jutho.github.io/TensorKit.jl/stable/man/tutorial/) and the full [documentation](https://jutho.github.io/TensorKit.jl/stable). + + +While most common symmetries are already shipped with TensorKit.jl, there exist several extensions: [SUNRepresentations.jl](https://github.com/maartenvd/SUNRepresentations.jl) provides support for SU(N), while [CategoryData.jl](https://github.com/lkdvos/CategoryData.jl) incorporates a large collection of *small* fusion categories. +Additionally, for libraries that implement tensor network algorithms on top of TensorKit.jl, check out [MPSKit.jl](https://github.com/maartenvd/MPSKit.jl), [MERAKit.jl](https://github.com/mhauru/MERAKit.jl) and [PEPSKit.jl](https://github.com/quantumghent/PEPSKit.jl). From cb4d921b2d18caaf9c90d2e9aff74fcbdfae5aa4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 4 Sep 2023 19:00:25 +0200 Subject: [PATCH 45/57] Bugfixes by @leburgel --- src/tensors/tensoroperations.jl | 2 +- src/tensors/vectorinterface.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index f567535b..0db7da38 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -26,7 +26,7 @@ function _canonicalize(p::Index2Tuple{N₁,N₂}, ::AbstractTensorMap{<:IndexSpace,N₁,N₂}) where {N₁,N₂} return p end -function _canonicalize(p::Index2Tuple, ::AbstractTensorMap) +function _canonicalize(p::Index2Tuple, t::AbstractTensorMap) p′ = linearize(p) p₁ = TupleTools.getindices(p′, codomainind(t)) p₂ = TupleTools.getindices(p′, domainind(t)) diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index dcae5284..b00793e8 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -8,7 +8,7 @@ VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = scalartype(storagetyp # zerovector & zerovector!! #--------------------------- function VectorInterface.zerovector(t::AbstractTensorMap, ::Type{S}) where {S<:Number} - return zero!(similar(t, S)) + return zerovector!(similar(t, S)) end function VectorInterface.zerovector!(t::AbstractTensorMap) for (c, b) in blocks(t) From b524237c4fd3639557cfa85697a9d36ca80b291f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 4 Sep 2023 19:30:25 +0200 Subject: [PATCH 46/57] Bump VectorInterface compat to 0.3.0 --- Project.toml | 2 +- src/tensors/vectorinterface.jl | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Project.toml b/Project.toml index c4c11529..18ff93b4 100644 --- a/Project.toml +++ b/Project.toml @@ -19,9 +19,9 @@ LRUCache = "1.0.2" Strided = "2" TensorOperations = "4.0.2" TupleTools = "1.1" +VectorInterface = "0.3" WignerSymbols = "1,2" julia = "1.6" -VectorInterface = "0.2" [extras] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index b00793e8..b1b31821 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -1,6 +1,3 @@ -using VectorInterface: ONumber, _one -_isone(α::ONumber) = α == _one || isone(α) - # scalartype #------------ VectorInterface.scalartype(T::Type{<:AbstractTensorMap}) = scalartype(storagetype(T)) @@ -20,15 +17,15 @@ VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) # scale, scale! & scale!! #------------------------- -VectorInterface.scale(t::TensorMap, α::ONumber) = _isone(α) ? t : t * α -function VectorInterface.scale!(t::AbstractTensorMap, α::ONumber) +VectorInterface.scale(t::TensorMap, α::Number) = isone(α) ? t : t * α +function VectorInterface.scale!(t::AbstractTensorMap, α::Number) for (c, b) in blocks(t) scale!(b, α) end return t end -function VectorInterface.scale!!(t::AbstractTensorMap, α::ONumber) - _isone(α) && return t +function VectorInterface.scale!!(t::AbstractTensorMap, α::Number) + isone(α) && return t if promote_type(scalartype(t), typeof(α)) <: scalartype(t) return scale!(t, α) else @@ -36,14 +33,14 @@ function VectorInterface.scale!!(t::AbstractTensorMap, α::ONumber) end end -function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber) +function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) space(ty) == space(tx) || throw(SpaceMismatch()) for c in blocksectors(tx) scale!(block(ty, c), block(tx, c), α) end return ty end -function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber) +function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) space(ty) == space(tx) || throw(SpaceMismatch()) T = scalartype(ty) if promote_type(T, typeof(α), scalartype(tx)) <: T @@ -56,14 +53,15 @@ end # add, add! & add!! #------------------- # TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed -function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::ONumber=_one, - β::ONumber=_one) +function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, + α::Number=VectorInterface._one, β::Number=VectorInterface._one) space(ty) == space(tx) || throw(SpaceMismatch()) T = promote_type(scalartype(ty), scalartype(tx), typeof(α), typeof(β)) return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::ONumber=_one, β::ONumber=_one) + α::Number=VectorInterface._one, + β::Number=VectorInterface._one) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) VectorInterface.add!(block(ty, c), block(tx, c), α, β) @@ -71,7 +69,8 @@ function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, return ty end function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::ONumber=_one, β::ONumber=_one) + α::Number=VectorInterface._one, + β::Number=VectorInterface._one) T = scalartype(ty) if promote_type(T, typeof(α), typeof(β), scalartype(tx)) <: T return VectorInterface.add!(ty, tx, α, β) From b891bde76c86ce1b95ca49657783a8a8df7c6534 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 5 Sep 2023 11:26:00 +0200 Subject: [PATCH 47/57] Update SpaceMismatch error messages --- src/tensors/braidingtensor.jl | 9 ++++----- src/tensors/linalg.jl | 14 +++++++++----- src/tensors/vectorinterface.jl | 8 ++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index bd19bf41..352c6cb8 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -191,9 +191,10 @@ function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - - if space(B, cindB[1]) != space(A, cindA[1])' || space(B, cindB[2]) != space(A, cindA[2]) - throw(SpaceMismatch()) + + if space(B, cindB[1]) != space(A, cindA[1])' || + space(B, cindB[2]) != space(A, cindA[2])' + throw(SpaceMismatch("$(space(C)) ≠ permute($(space(A))[$oindA, $cindA] * $(space(B))[$cindB, $oindB], ($p1, $p2)")) end if BraidingStyle(sectortype(B)) isa Bosonic @@ -239,8 +240,6 @@ function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, if space(B, cindB[1]) != space(A, cindA[1])' || space(B, cindB[2]) != space(A, cindA[2])' - # @show space(B, cindB[1]), space(A, cindA[1]) - # @show space(B, cindB[2]), space(A, cindA[2]) throw(SpaceMismatch("$(space(C)) ≠ permute($(space(A))[$oindA, $cindA] * $(space(B))[$cindB, $oindB], ($p1, $p2)")) end diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 3545bfc8..044f6ad8 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -163,7 +163,7 @@ end # Wrapping the blocks in a StridedView enables multithreading if JULIA_NUM_THREADS > 1 # Copy, adjoint! and fill: function Base.copy!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) - space(tdst) == space(tsrc) || throw(SpaceMismatch()) + space(tdst) == space(tsrc) || throw(SpaceMismatch("$(space(tdst)) ≠ $(space(tsrc))")) for c in blocksectors(tdst) copy!(StridedView(block(tdst, c)), StridedView(block(tsrc, c))) end @@ -179,7 +179,8 @@ function LinearAlgebra.adjoint!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) spacetype(tdst) === spacetype(tsrc) && InnerProductStyle(tdst) === EuclideanProduct() || throw(ArgumentError("adjoint! requires Euclidean inner product spacetype")) - space(tdst) == adjoint(space(tsrc)) || throw(SpaceMismatch()) + space(tdst) == adjoint(space(tsrc)) || + throw(SpaceMismatch("$(space(tdst)) ≠ adjoint($(space(tsrc)))")) for c in blocksectors(tdst) adjoint!(StridedView(block(tdst, c)), StridedView(block(tsrc, c))) end @@ -396,8 +397,9 @@ end # concatenate tensors function catdomain(t1::AbstractTensorMap{S,N₁,1}, t2::AbstractTensorMap{S,N₁,1}) where {S,N₁} - codomain(t1) == codomain(t2) || throw(SpaceMismatch()) - + codomain(t1) == codomain(t2) || + throw(SpaceMismatch("codomains of tensors to concatenate must match:\n\ + $(codomain(t1)) ≠ $(codomain(t2))")) V1, = domain(t1) V2, = domain(t2) isdual(V1) == isdual(V2) || @@ -413,7 +415,9 @@ function catdomain(t1::AbstractTensorMap{S,N₁,1}, end function catcodomain(t1::AbstractTensorMap{S,1,N₂}, t2::AbstractTensorMap{S,1,N₂}) where {S,N₂} - domain(t1) == domain(t2) || throw(SpaceMismatch()) + domain(t1) == domain(t2) || + throw(SpaceMismatch("domains of tensors to concatenate must match:\n\ + $(domain(t1)) ≠ $(domain(t2))")) V1, = codomain(t1) V2, = codomain(t2) diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index b1b31821..efa7d3ec 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -34,14 +34,14 @@ function VectorInterface.scale!!(t::AbstractTensorMap, α::Number) end function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) - space(ty) == space(tx) || throw(SpaceMismatch()) + space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) scale!(block(ty, c), block(tx, c), α) end return ty end function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) - space(ty) == space(tx) || throw(SpaceMismatch()) + space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) T = scalartype(ty) if promote_type(T, typeof(α), scalartype(tx)) <: T return scale!(ty, tx, α) @@ -55,7 +55,7 @@ end # TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number=VectorInterface._one, β::Number=VectorInterface._one) - space(ty) == space(tx) || throw(SpaceMismatch()) + space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) T = promote_type(scalartype(ty), scalartype(tx), typeof(α), typeof(β)) return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end @@ -82,7 +82,7 @@ end # inner #------- function VectorInterface.inner(tx::AbstractTensorMap, ty::AbstractTensorMap) - space(tx) == space(ty) || throw(SpaceMismatch()) + space(tx) == space(ty) || throw(SpaceMismatch("$(space(tx)) ≠ $(space(ty))")) InnerProductStyle(tx) === EuclideanProduct() || throw(ArgumentError("dot requires Euclidean inner product")) T = promote_type(scalartype(tx), scalartype(ty)) From 3ed8f12b7979e41cbaf9032d564f6542ee9011aa Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 5 Sep 2023 15:27:07 +0200 Subject: [PATCH 48/57] Update VectorInterface implementations to improve scalartype stability --- src/TensorKit.jl | 1 + src/tensors/linalg.jl | 30 +++++++++------------------ src/tensors/vectorinterface.jl | 37 +++++++++++++++++----------------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 944ade4f..ac10b142 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -92,6 +92,7 @@ using TupleTools: StaticLength using Strided using VectorInterface +using VectorInterface: _zero, _one using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon using TensorOperations: IndexTuple, Index2Tuple, linearize, Backend diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 044f6ad8..c5ef2224 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -2,29 +2,19 @@ #--------------- Base.copy(t::AbstractTensorMap) = Base.copy!(similar(t), t) -Base.:-(t::AbstractTensorMap) = mul!(similar(t), t, -one(scalartype(t))) -function Base.:+(t1::AbstractTensorMap, t2::AbstractTensorMap) - T = promote_type(scalartype(t1), scalartype(t2)) - return axpy!(one(T), t2, copy!(similar(t1, T), t1)) -end -function Base.:-(t1::AbstractTensorMap, t2::AbstractTensorMap) - T = promote_type(scalartype(t1), scalartype(t2)) - return axpy!(-one(T), t2, copy!(similar(t1, T), t1)) -end +Base.:-(t::AbstractTensorMap) = VectorInterface.scale(t, -one(scalartype(t))) + +Base.:+(t1::AbstractTensorMap, t2::AbstractTensorMap) = VectorInterface.add(t1, t2) +Base.:-(t1::AbstractTensorMap, t2::AbstractTensorMap) = VectorInterface.add(t1, t2, -one(scalartype(t1))) + +Base.:*(t::AbstractTensorMap, α::Number) = VectorInterface.scale(t, α) +Base.:*(α::Number, t::AbstractTensorMap) = VectorInterface.scale(t, α) -function Base.:*(t::AbstractTensorMap, α::Number) - return mul!(similar(t, promote_type(scalartype(t), typeof(α))), t, α) -end -function Base.:*(α::Number, t::AbstractTensorMap) - return mul!(similar(t, promote_type(scalartype(t), typeof(α))), α, t) -end Base.:/(t::AbstractTensorMap, α::Number) = *(t, one(scalartype(t)) / α) Base.:\(α::Number, t::AbstractTensorMap) = *(t, one(scalartype(t)) / α) -LinearAlgebra.normalize!(t::AbstractTensorMap, p::Real=2) = rmul!(t, inv(norm(t, p))) -function LinearAlgebra.normalize(t::AbstractTensorMap, p::Real=2) - return mul!(similar(t), t, inv(norm(t, p))) -end +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) return mul!(similar(t1, promote_type(scalartype(t1), scalartype(t2)), @@ -37,7 +27,7 @@ end # Special purpose constructors #------------------------------ -Base.zero(t::AbstractTensorMap) = fill!(similar(t), 0) +Base.zero(t::AbstractTensorMap) = VectorInterface.zerovector(t) function Base.one(t::AbstractTensorMap) domain(t) == codomain(t) || throw(SectorMismatch("no identity if domain and codomain are different")) diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index efa7d3ec..68b5d783 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -17,7 +17,10 @@ VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) # scale, scale! & scale!! #------------------------- -VectorInterface.scale(t::TensorMap, α::Number) = isone(α) ? t : t * α +function VectorInterface.scale(t::AbstractTensorMap, α::Number) + T = Base.promote_op(scale, scalartype(t), scalartype(α)) + return scale!(similar(t, T), t, α) +end function VectorInterface.scale!(t::AbstractTensorMap, α::Number) for (c, b) in blocks(t) scale!(b, α) @@ -25,12 +28,10 @@ function VectorInterface.scale!(t::AbstractTensorMap, α::Number) return t end function VectorInterface.scale!!(t::AbstractTensorMap, α::Number) - isone(α) && return t - if promote_type(scalartype(t), typeof(α)) <: scalartype(t) - return scale!(t, α) - else - return scale(t, α) - end + α === _one && return t + α === _zero && return zerovector!!(t) + T = Base.promote_op(scale, scalartype(t), scalartype(α)) + return T <: scalartype(t) ? scale!(t, α) : scale(t, α) end function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) @@ -41,9 +42,8 @@ function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α return ty end function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) - space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) - T = scalartype(ty) - if promote_type(T, typeof(α), scalartype(tx)) <: T + T = Base.promote_op(scale, scalartype(tx), scalartype(α)) + if T <: scalartype(ty) return scale!(ty, tx, α) else return scale(tx, α) @@ -54,14 +54,13 @@ end #------------------- # TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=VectorInterface._one, β::Number=VectorInterface._one) + α::Number=_one, β::Number=_one) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) - T = promote_type(scalartype(ty), scalartype(tx), typeof(α), typeof(β)) + T = Base.promote_op(VectorInterface.add, scalartype(ty), scalartype(tx), scalartype(α), scalartype(β)) return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=VectorInterface._one, - β::Number=VectorInterface._one) + α::Number=_one, β::Number=_one) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) VectorInterface.add!(block(ty, c), block(tx, c), α, β) @@ -69,10 +68,10 @@ function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, return ty end function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=VectorInterface._one, - β::Number=VectorInterface._one) - T = scalartype(ty) - if promote_type(T, typeof(α), typeof(β), scalartype(tx)) <: T + α::Number=_one, β::Number=_one) + T = Base.promote_op(VectorInterface.add, scalartype(ty), scalartype(tx), scalartype(α), + scalartype(β)) + if T <: scalartype(ty) return VectorInterface.add!(ty, tx, α, β) else return VectorInterface.add(ty, tx, α, β) @@ -85,7 +84,7 @@ function VectorInterface.inner(tx::AbstractTensorMap, ty::AbstractTensorMap) space(tx) == space(ty) || throw(SpaceMismatch("$(space(tx)) ≠ $(space(ty))")) InnerProductStyle(tx) === EuclideanProduct() || throw(ArgumentError("dot requires Euclidean inner product")) - T = promote_type(scalartype(tx), scalartype(ty)) + T = Base.promote_op(VectorInterface.inner, scalartype(tx), scalartype(ty)) s = zero(T) for c in blocksectors(tx) s += convert(T, dim(c)) * dot(block(tx, c), block(ty, c)) From 02e1bebf978b9084c9d964c126d395eb462fccda Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 19 Sep 2023 22:37:51 +0200 Subject: [PATCH 49/57] Update github actions --- .github/dependabot.yml | 7 ++ .github/workflows/CI.yml | 72 +++++++++++++++++++ .github/workflows/CompatHelper.yml | 18 +++++ .../workflows/{docs.yml => Documentation.yml} | 21 ++++-- .../{format_check.yml => FormatCheck.yml} | 19 +++-- .github/workflows/TagBot.yml | 2 + .github/workflows/ci-julia-nightly.yml | 34 --------- .github/workflows/ci.yml | 35 --------- 8 files changed, 128 insertions(+), 80 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CI.yml create mode 100644 .github/workflows/CompatHelper.yml rename .github/workflows/{docs.yml => Documentation.yml} (60%) rename .github/workflows/{format_check.yml => FormatCheck.yml} (74%) delete mode 100644 .github/workflows/ci-julia-nightly.yml delete mode 100644 .github/workflows/ci.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ff6499d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..cf776f42 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,72 @@ +name: CI +on: + push: + branches: + - 'master' + - 'main' + - 'release-' + tags: '*' + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.6' # LTS version + - '1' # automatically expands to the latest stable 1.x release of Julia + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v1 + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-runtest@latest + env: + JULIA_NUM_THREADS: 4 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v3 + with: + file: lcov.info + test-nightly: + needs: test + name: Julia nightly - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - 'nightly' + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v1 + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-runtest@latest + env: + JULIA_NUM_THREADS: 4 diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 00000000..187a2933 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,18 @@ +name: CompatHelper + +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: + +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/docs.yml b/.github/workflows/Documentation.yml similarity index 60% rename from .github/workflows/docs.yml rename to .github/workflows/Documentation.yml index 7f0b3f3e..72f40322 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/Documentation.yml @@ -1,18 +1,31 @@ name: Documentation + on: push: branches: - - master + - 'master' + - 'main' + - 'release-' tags: '*' pull_request: + jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + version: + - '1' # automatically expands to the latest stable 1.x release of Julia + os: + - ubuntu-latest + arch: + - x64 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1' + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/.github/workflows/format_check.yml b/.github/workflows/FormatCheck.yml similarity index 74% rename from .github/workflows/format_check.yml rename to .github/workflows/FormatCheck.yml index eb7d11d6..c3ed1200 100644 --- a/.github/workflows/format_check.yml +++ b/.github/workflows/FormatCheck.yml @@ -1,8 +1,9 @@ -name: format-check +name: FormatCheck on: push: branches: + - 'main' - 'master' - 'release-' tags: '*' @@ -13,15 +14,19 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: [1] - julia-arch: [x86] - os: [ubuntu-latest] + version: + - '1' # automatically expands to the latest stable 1.x release of Julia + os: + - ubuntu-latest + arch: + - x64 steps: - uses: julia-actions/setup-julia@latest with: - version: ${{ matrix.julia-version }} + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Install JuliaFormatter and format # This will use the latest version by default but you can set the version like so: # @@ -39,4 +44,4 @@ jobs: @error "Some files have not been formatted !!!" write(stdout, out) exit(1) - end' \ No newline at end of file + end' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index f49313b6..6d2efc1c 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,9 +1,11 @@ name: TagBot + on: issue_comment: types: - created workflow_dispatch: + jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml deleted file mode 100644 index fe980d3d..00000000 --- a/.github/workflows/ci-julia-nightly.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI (Julia nightly) -on: - - push - - pull_request -jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - version: - - 'nightly' - os: - - ubuntu-latest - - macOS-latest - - windows-latest - arch: - - x64 - steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v1 - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - uses: julia-actions/cache@v1 - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest - # env: - # JULIA_NUM_THREADS: 2 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v3 - with: - file: lcov.info diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index afa09d8b..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: CI -on: - - push - - pull_request -jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - version: - - '1.6' - - '1' - os: - - ubuntu-latest - - macOS-latest - - windows-latest - arch: - - x64 - steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v1 - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - uses: julia-actions/cache@v1 - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest - # env: - # JULIA_NUM_THREADS: 2 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v3 - with: - file: lcov.info From 2f9c7f109603be3b3694d54446e259d882feed62 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 19 Sep 2023 22:50:42 +0200 Subject: [PATCH 50/57] Incorporate VectorInterface updates --- src/TensorKit.jl | 3 --- src/tensors/tensoroperations.jl | 8 ++------ src/tensors/vectorinterface.jl | 24 +++++++++++------------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index ac10b142..b30e26e7 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -92,7 +92,6 @@ using TupleTools: StaticLength using Strided using VectorInterface -using VectorInterface: _zero, _one using TensorOperations: TensorOperations, @tensor, @tensoropt, @ncon, ncon using TensorOperations: IndexTuple, Index2Tuple, linearize, Backend @@ -118,8 +117,6 @@ using LinearAlgebra: norm, dot, normalize, normalize!, tr, Diagonal, Hermitian import Base.Meta -# const IndexTuple{N} = NTuple{N, Int} - # Auxiliary files #----------------- include("auxiliary/auxiliary.jl") diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 0db7da38..b7fa6cb7 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -188,11 +188,7 @@ function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, cod = codomain(tsrc) dom = domain(tsrc) n = length(cod) - if iszero(β) - fill!(tdst, β) - elseif β != 1 - mul!(tdst, β, tdst) - end + scale!(tdst, β) r₁ = (p₁..., q₁...) r₂ = (p₂..., q₂...) for (f₁, f₂) in fusiontrees(tsrc) @@ -209,7 +205,7 @@ function trace_permute!(tdst::AbstractTensorMap{S,N₁,N₂}, C = tdst[f₁′′, f₂′′] A = tsrc[f₁, f₂] α′ = α * coeff - TO.tensortrace!(C, (p₁, p₂), A, (q₁, q₂), :N, α′, true, backend...) + TO.tensortrace!(C, (p₁, p₂), A, (q₁, q₂), :N, α′, One(), backend...) end end end diff --git a/src/tensors/vectorinterface.jl b/src/tensors/vectorinterface.jl index 68b5d783..9462fc90 100644 --- a/src/tensors/vectorinterface.jl +++ b/src/tensors/vectorinterface.jl @@ -18,7 +18,7 @@ VectorInterface.zerovector!!(t::AbstractTensorMap) = zerovector!(t) # scale, scale! & scale!! #------------------------- function VectorInterface.scale(t::AbstractTensorMap, α::Number) - T = Base.promote_op(scale, scalartype(t), scalartype(α)) + T = VectorInterface.promote_scale(t, α) return scale!(similar(t, T), t, α) end function VectorInterface.scale!(t::AbstractTensorMap, α::Number) @@ -28,12 +28,10 @@ function VectorInterface.scale!(t::AbstractTensorMap, α::Number) return t end function VectorInterface.scale!!(t::AbstractTensorMap, α::Number) - α === _one && return t - α === _zero && return zerovector!!(t) - T = Base.promote_op(scale, scalartype(t), scalartype(α)) + α === One() && return t + T = VectorInterface.promote_scale(t, α) return T <: scalartype(t) ? scale!(t, α) : scale(t, α) end - function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) @@ -42,7 +40,7 @@ function VectorInterface.scale!(ty::AbstractTensorMap, tx::AbstractTensorMap, α return ty end function VectorInterface.scale!!(ty::AbstractTensorMap, tx::AbstractTensorMap, α::Number) - T = Base.promote_op(scale, scalartype(tx), scalartype(α)) + T = VectorInterface.promote_scale(tx, α) if T <: scalartype(ty) return scale!(ty, tx, α) else @@ -54,13 +52,13 @@ end #------------------- # TODO: remove VectorInterface from calls to `add!` when `TensorKit.add!` is renamed function VectorInterface.add(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=_one, β::Number=_one) + α::Number, β::Number) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) - T = Base.promote_op(VectorInterface.add, scalartype(ty), scalartype(tx), scalartype(α), scalartype(β)) + T = VectorInterface.promote_add(ty, tx, α, β) return VectorInterface.add!(scale!(similar(ty, T), ty, β), tx, α) end function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=_one, β::Number=_one) + α::Number, β::Number) space(ty) == space(tx) || throw(SpaceMismatch("$(space(ty)) ≠ $(space(tx))")) for c in blocksectors(tx) VectorInterface.add!(block(ty, c), block(tx, c), α, β) @@ -68,9 +66,9 @@ function VectorInterface.add!(ty::AbstractTensorMap, tx::AbstractTensorMap, return ty end function VectorInterface.add!!(ty::AbstractTensorMap, tx::AbstractTensorMap, - α::Number=_one, β::Number=_one) - T = Base.promote_op(VectorInterface.add, scalartype(ty), scalartype(tx), scalartype(α), - scalartype(β)) + α::Number, β::Number) + # spacecheck is done in add(!) + T = VectorInterface.promote_add(ty, tx, α, β) if T <: scalartype(ty) return VectorInterface.add!(ty, tx, α, β) else @@ -84,7 +82,7 @@ function VectorInterface.inner(tx::AbstractTensorMap, ty::AbstractTensorMap) space(tx) == space(ty) || throw(SpaceMismatch("$(space(tx)) ≠ $(space(ty))")) InnerProductStyle(tx) === EuclideanProduct() || throw(ArgumentError("dot requires Euclidean inner product")) - T = Base.promote_op(VectorInterface.inner, scalartype(tx), scalartype(ty)) + T = VectorInterface.promote_inner(tx, ty) s = zero(T) for c in blocksectors(tx) s += convert(T, dim(c)) * dot(block(tx, c), block(ty, c)) From 03264b51f3d8fd02fb34d4f0ddd2217f11bb210b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 19 Sep 2023 22:51:26 +0200 Subject: [PATCH 51/57] Formatter --- src/tensors/braidingtensor.jl | 2 +- src/tensors/linalg.jl | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 352c6cb8..121ba7e2 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -191,7 +191,7 @@ function planarcontract!(C::AbstractTensorMap{S,N₁,N₂}, codB, domB = codomainind(B), domainind(B) oindA, cindA, oindB, cindB = reorder_indices(codA, domA, codB, domB, oindA, cindA, oindB, cindB, p1, p2) - + if space(B, cindB[1]) != space(A, cindA[1])' || space(B, cindB[2]) != space(A, cindA[2])' throw(SpaceMismatch("$(space(C)) ≠ permute($(space(A))[$oindA, $cindA] * $(space(B))[$cindB, $oindB], ($p1, $p2)")) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index c5ef2224..757363f8 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -5,7 +5,9 @@ Base.copy(t::AbstractTensorMap) = Base.copy!(similar(t), t) Base.:-(t::AbstractTensorMap) = VectorInterface.scale(t, -one(scalartype(t))) Base.:+(t1::AbstractTensorMap, t2::AbstractTensorMap) = VectorInterface.add(t1, t2) -Base.:-(t1::AbstractTensorMap, t2::AbstractTensorMap) = VectorInterface.add(t1, t2, -one(scalartype(t1))) +function Base.:-(t1::AbstractTensorMap, t2::AbstractTensorMap) + return VectorInterface.add(t1, t2, -one(scalartype(t1))) +end Base.:*(t::AbstractTensorMap, α::Number) = VectorInterface.scale(t, α) Base.:*(α::Number, t::AbstractTensorMap) = VectorInterface.scale(t, α) @@ -169,7 +171,7 @@ function LinearAlgebra.adjoint!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap) spacetype(tdst) === spacetype(tsrc) && InnerProductStyle(tdst) === EuclideanProduct() || throw(ArgumentError("adjoint! requires Euclidean inner product spacetype")) - space(tdst) == adjoint(space(tsrc)) || + space(tdst) == adjoint(space(tsrc)) || throw(SpaceMismatch("$(space(tdst)) ≠ adjoint($(space(tsrc)))")) for c in blocksectors(tdst) adjoint!(StridedView(block(tdst, c)), StridedView(block(tsrc, c))) From 32a5cc40754ad501e601890fcbf76a2bcb1ccc05 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 19 Sep 2023 22:53:12 +0200 Subject: [PATCH 52/57] Remove julia-nightly badge --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 04e2643b..e8c6055b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A Julia package for large-scale tensor computations, with a hint of category the | **Build Status** | **Coverage** | **Quality assurance** | **Downloads** | |:----------------:|:------------:|:---------------------:|:--------------| -| [![CI][ci-img]][ci-url] [![CI (Julia nightly)][ci-julia-nightly-img]][ci-julia-nightly-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [![TensorKit Downloads][genie-img]][genie-url] | +| [![CI][ci-img]][ci-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [![TensorKit Downloads][genie-img]][genie-url] | [github-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI/badge.svg [github-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3ACI @@ -17,11 +17,6 @@ A Julia package for large-scale tensor computations, with a hint of category the [ci-img]: https://github.com/Jutho/TensorKit.jl/workflows/CI/badge.svg [ci-url]: https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3ACI -[ci-julia-nightly-img]: - https://github.com/Jutho/TensorKit.jl/workflows/CI%20(Julia%20nightly)/badge.svg -[ci-julia-nightly-url]: - https://github.com/Jutho/TensorKit.jl/actions?query=workflow%3A%22CI+%28Julia+nightly%29%22 - [codecov-img]: https://codecov.io/gh/Jutho/TensorKit.jl/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/Jutho/TensorKit.jl From 7f907f383ec0f99af1a55289a3e096011b7fac06 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 19 Sep 2023 22:54:56 +0200 Subject: [PATCH 53/57] Update VectorInterface Compat --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 18ff93b4..d0736c2c 100644 --- a/Project.toml +++ b/Project.toml @@ -17,9 +17,9 @@ WignerSymbols = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" HalfIntegers = "1" LRUCache = "1.0.2" Strided = "2" -TensorOperations = "4.0.2" +TensorOperations = "4.0.5" TupleTools = "1.1" -VectorInterface = "0.3" +VectorInterface = "0.4" WignerSymbols = "1,2" julia = "1.6" From e476b83f23e8ed690bdcffd1e6cd81b33f9dc7f2 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 20 Sep 2023 17:36:07 +0200 Subject: [PATCH 54/57] Fix invalid syntax for julia 1.6 --- src/tensors/linalg.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 757363f8..c66be7e4 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -390,8 +390,8 @@ end function catdomain(t1::AbstractTensorMap{S,N₁,1}, t2::AbstractTensorMap{S,N₁,1}) where {S,N₁} codomain(t1) == codomain(t2) || - throw(SpaceMismatch("codomains of tensors to concatenate must match:\n\ - $(codomain(t1)) ≠ $(codomain(t2))")) + throw(SpaceMismatch("codomains of tensors to concatenate must match:\n" * + "$(codomain(t1)) ≠ $(codomain(t2))")) V1, = domain(t1) V2, = domain(t2) isdual(V1) == isdual(V2) || @@ -408,9 +408,8 @@ end function catcodomain(t1::AbstractTensorMap{S,1,N₂}, t2::AbstractTensorMap{S,1,N₂}) where {S,N₂} domain(t1) == domain(t2) || - throw(SpaceMismatch("domains of tensors to concatenate must match:\n\ - $(domain(t1)) ≠ $(domain(t2))")) - + throw(SpaceMismatch("domains of tensors to concatenate must match:\n" * + "$(domain(t1)) ≠ $(domain(t2))")) V1, = codomain(t1) V2, = codomain(t2) isdual(V1) == isdual(V2) || From 5ae38118140fdb349be3048978ddede78eff79e0 Mon Sep 17 00:00:00 2001 From: Jutho Date: Fri, 1 Sep 2023 22:43:24 +0200 Subject: [PATCH 55/57] remove commented out code --- src/tensors/indexmanipulations.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 6b91145b..9dea8d58 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -340,20 +340,6 @@ function _add_general_kernel!(tdst, tsrc, p, fusiontreetransform, α, β, backen end end end - - # if Threads.nthreads() > 1 - # Threads.@sync for s₁ in sectors(codomain(tsrc)), s₂ in sectors(domain(tsrc)) - # _add_sectors!(tdst, tsrc, fusiontreemap, s₁, s₂, α, β, backend...) - # end - # 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...) - # end - # end - # end - return nothing end From 81a2b571e6d481efb79c05b18e99760bfc1749e7 Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Thu, 21 Sep 2023 17:04:20 +0200 Subject: [PATCH 56/57] change factorization calling syntax; bump patch --- Project.toml | 2 +- src/auxiliary/deprecate.jl | 24 ++++++++++++ src/tensors/factorizations.jl | 69 ++++++++++++++--------------------- test/tensors.jl | 14 +++---- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/Project.toml b/Project.toml index d0736c2c..1850e74c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TensorKit" uuid = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" authors = ["Jutho Haegeman"] -version = "0.11.1" +version = "0.11.2" [deps] HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index b9c5246d..7e282c64 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -6,4 +6,28 @@ import Base: eltype, transpose @deprecate permute(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) permute(t, (p1, p2); copy=copy) @deprecate transpose(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; copy::Bool=false) transpose(t, (p1, p2); copy=copy) @deprecate braid(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple, levels; copy::Bool=false) braid(t, (p1, p2), levels; copy=copy) + + +Base.@deprecate(svd(t::AbstractTensorMap, leftind::IndexTuple, rightind::IndexTuple; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t, (leftind, rightind); trunc=trunc, p=p, alg=alg)) +Base.@deprecate(svd(t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t; trunc=trunc, p=p, alg=alg)) +Base.@deprecate(svd!(t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), + tsvd(t; trunc=trunc, p=p, alg=alg)) + + +# TODO: deprecate + +tsvd(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = tsvd(t, (p₁, p₂); kwargs...) +leftorth(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = leftorth(t, (p₁, p₂); kwargs...) +rightorth(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = rightorth(t, (p₁, p₂); kwargs...) +leftnull(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = leftnull(t, (p₁, p₂); kwargs...) +rightnull(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = rightnull(t, (p₁, p₂); kwargs...) +LinearAlgebra.eigen(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = LinearAlgebra.eigen(t, (p₁, p₂); kwargs...) +eig(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = eig(t, (p₁, p₂); kwargs...) +eigh(t::AbstractTensorMap, p₁::IndexTuple, p₂::IndexTuple; kwargs...) = eigh(t, (p₁, p₂); kwargs...) + #! format: on diff --git a/src/tensors/factorizations.jl b/src/tensors/factorizations.jl index 0c7a9009..15084cab 100644 --- a/src/tensors/factorizations.jl +++ b/src/tensors/factorizations.jl @@ -5,18 +5,8 @@ const OFA = OrthogonalFactorizationAlgorithm import LinearAlgebra: svd!, svd const SVDAlg = Union{SVD,SDD} -Base.@deprecate(svd(t::AbstractTensorMap, leftind::IndexTuple, rightind::IndexTuple; - trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), - tsvd(t, leftind, rightind; trunc=trunc, p=p, alg=alg)) -Base.@deprecate(svd(t::AbstractTensorMap; - trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), - tsvd(t; trunc=trunc, p=p, alg=alg)) -Base.@deprecate(svd!(t::AbstractTensorMap; - trunc::TruncationScheme=notrunc(), p::Real=2, alg::SVDAlg=SDD()), - tsvd(t; trunc=trunc, p=p, alg=alg)) - """ - tsvd(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; + tsvd(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple; trunc::TruncationScheme = notrunc(), p::Real = 2, alg::Union{SVD, SDD} = SDD()) -> U, S, V, ϵ @@ -47,12 +37,12 @@ algorithm that computes the decomposition (`_gesvd` or `_gesdd`). Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `tsvd(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -function tsvd(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return tsvd!(permute(t, (p1, p2); copy=true); kwargs...) +function tsvd(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return tsvd!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - leftorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; + leftorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple; alg::OrthogonalFactorizationAlgorithm = QRpos()) -> Q, R Create orthonormal basis `Q` for indices in `leftind`, and remainder `R` such that @@ -73,12 +63,12 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `leftorth(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -function leftorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return leftorth!(permute(t, (p1, p2); copy=true); kwargs...) +function leftorth(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return leftorth!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - rightorth(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple; + rightorth(t::AbstractTensorMap, (leftind, rightind)::Index2Tuple; alg::OrthogonalFactorizationAlgorithm = LQpos()) -> L, Q Create orthonormal basis `Q` for indices in `rightind`, and remainder `L` such that @@ -101,12 +91,12 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `rightorth(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -function rightorth(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return rightorth!(permute(t, (p1, p2); copy=true); kwargs...) +function rightorth(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return rightorth!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - leftnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; + leftnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple; alg::OrthogonalFactorizationAlgorithm = QRpos()) -> N Create orthonormal basis for the orthogonal complement of the support of the indices in @@ -127,12 +117,12 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `leftnull(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -function leftnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return leftnull!(permute(t, (p1, p2); copy=true); kwargs...) +function leftnull(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return leftnull!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - rightnull(t::AbstractTensor, leftind::Tuple, rightind::Tuple; + rightnull(t::AbstractTensor, (leftind, rightind)::Index2Tuple; alg::OrthogonalFactorizationAlgorithm = LQ(), atol::Real = 0.0, rtol::Real = eps(real(float(one(scalartype(t)))))*iszero(atol)) -> N @@ -155,12 +145,12 @@ Orthogonality requires `InnerProductStyle(t) <: HasInnerProduct`, and `rightnull(!)` is currently only implemented for `InnerProductStyle(t) === EuclideanProduct()`. """ -function rightnull(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return rightnull!(permute(t, (p1, p2); copy=true); kwargs...) +function rightnull(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return rightnull!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - eigen(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V + eigen(t::AbstractTensor, (leftind, rightind)::Index2Tuple; kwargs...) -> D, V Compute eigenvalue factorization of tensor `t` as linear map from `rightind` to `leftind`. @@ -178,13 +168,13 @@ matrices. See the corresponding documentation for more information. See also `eig` and `eigh` """ -function LinearAlgebra.eigen(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; +function LinearAlgebra.eigen(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) - return eigen!(permute(t, (p1, p2); copy=true); kwargs...) + return eigen!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - eig(t::AbstractTensor, leftind::Tuple, rightind::Tuple; kwargs...) -> D, V + 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 @@ -204,12 +194,12 @@ Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of See also `eigen` and `eigh`. """ -function eig(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple; kwargs...) - return eig!(permute(t, (p1, p2); copy=true); kwargs...) +function eig(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple; kwargs...) + return eig!(permute(t, (p₁, p₂); copy=true); kwargs...) end """ - eigh(t::AbstractTensorMap, leftind::Tuple, rightind::Tuple) -> D, V + 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 @@ -228,12 +218,12 @@ permute(t, (leftind, rightind)) * V = V * D See also `eigen` and `eig`. """ -function eigh(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) - return eigh!(permute(t, (p1, p2); copy=true)) +function eigh(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple) + return eigh!(permute(t, (p₁, p₂); copy=true)) end """ - isposdef(t::AbstractTensor, leftind::Tuple, rightind::Tuple) -> ::Bool + isposdef(t::AbstractTensor, (leftind, rightind)::Index2Tuple) -> ::Bool Test whether a tensor `t` is positive definite as linear map from `rightind` to `leftind`. @@ -241,13 +231,10 @@ If `leftind` and `rightind` are not specified, the current partition of left and 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 - -Accepts the same keyword arguments `scale`, `permute` and `sortby` as `eigen` of dense -matrices. See the corresponding documentation for more information. +meaningless. """ -function LinearAlgebra.isposdef(t::AbstractTensorMap, p1::IndexTuple, p2::IndexTuple) - return isposdef!(permute(t, (p1, p2); copy=true)) +function LinearAlgebra.isposdef(t::AbstractTensorMap, (p₁, p₂)::Index2Tuple) + return isposdef!(permute(t, (p₁, p₂); copy=true)) end function tsvd(t::AbstractTensorMap; trunc::TruncationScheme=NoTruncation(), diff --git a/test/tensors.jl b/test/tensors.jl index 0a1d8edf..b6d7b6db 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -347,7 +347,7 @@ for V in spacelist TensorKit.QL(), TensorKit.QLpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - Q, R = @constinferred leftorth(t, (3, 4, 2), (1, 5); alg=alg) + Q, R = @constinferred leftorth(t, ((3, 4, 2), (1, 5)); alg=alg) QdQ = Q' * Q @test QdQ ≈ one(QdQ) @test Q * R ≈ permute(t, ((3, 4, 2), (1, 5))) @@ -359,7 +359,7 @@ for V in spacelist @testset "leftnull with $alg" for alg in (TensorKit.QR(), TensorKit.SVD(), TensorKit.SDD()) - N = @constinferred leftnull(t, (3, 4, 2), (1, 5); alg=alg) + N = @constinferred leftnull(t, ((3, 4, 2), (1, 5)); alg=alg) NdN = N' * N @test NdN ≈ one(NdN) @test norm(N' * permute(t, ((3, 4, 2), (1, 5)))) < @@ -370,7 +370,7 @@ for V in spacelist TensorKit.LQ(), TensorKit.LQpos(), TensorKit.Polar(), TensorKit.SVD(), TensorKit.SDD()) - L, Q = @constinferred rightorth(t, (3, 4), (2, 1, 5); alg=alg) + L, Q = @constinferred rightorth(t, ((3, 4), (2, 1, 5)); alg=alg) QQd = Q * Q' @test QQd ≈ one(QQd) @test L * Q ≈ permute(t, ((3, 4), (2, 1, 5))) @@ -382,14 +382,14 @@ for V in spacelist @testset "rightnull with $alg" for alg in (TensorKit.LQ(), TensorKit.SVD(), TensorKit.SDD()) - M = @constinferred rightnull(t, (3, 4), (2, 1, 5); alg=alg) + M = @constinferred rightnull(t, ((3, 4), (2, 1, 5)); alg=alg) MMd = M * M' @test MMd ≈ one(MMd) @test norm(permute(t, ((3, 4), (2, 1, 5))) * M') < 100 * eps(norm(t)) end @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) - U, S, V = @constinferred tsvd(t, (3, 4, 2), (1, 5); alg=alg) + U, S, V = @constinferred tsvd(t, ((3, 4, 2), (1, 5)); alg=alg) UdU = U' * U @test UdU ≈ one(UdU) VVd = V * V' @@ -440,8 +440,8 @@ for V in spacelist t = Tensor(rand, T, V1 ⊗ V1' ⊗ V2 ⊗ V2') @testset "eig and isposdef" begin - D, V = eigen(t, (1, 3), (2, 4)) - D̃, Ṽ = @constinferred eig(t, (1, 3), (2, 4)) + D, V = eigen(t, ((1, 3), (2, 4))) + D̃, Ṽ = @constinferred eig(t, ((1, 3), (2, 4))) @test D ≈ D̃ @test V ≈ Ṽ VdV = V' * V From 71da49180fee89f60ac5fb598b61d4eed6d2a2f9 Mon Sep 17 00:00:00 2001 From: Jutho Haegeman Date: Fri, 22 Sep 2023 14:05:44 +0200 Subject: [PATCH 57/57] update CI, try appveyor for windows --- .appveyor.yml | 36 +++++++++++++++++++++++++++++++ .github/workflows/CI.yml | 6 +++++- .github/workflows/TagBot.yml | 17 +++++++++++++++ LICENSE.md | 41 ++++++++++++++++++------------------ 4 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..abbe2e06 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,36 @@ +# Documentation: https://github.com/JuliaCI/Appveyor.jl +environment: + matrix: + - julia_version: 1.6 + - julia_version: 1.9 + - julia_version: nightly +platform: + - x64 + - x86 +cache: + - '%USERPROFILE%\.julia\artifacts' +matrix: + allow_failures: + - julia_version: nightly +branches: + only: + - main + - master + - /release-.*/ +notifications: + - provider: Email + on_build_success: false + on_build_failure: false + on_build_status_changed: false +install: + - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) +build_script: + - echo "%JL_BUILD_SCRIPT%" + - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" +test_script: + - echo "%JL_TEST_SCRIPT%" + - set JULIA_NUM_THREADS=2 + - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" +on_success: + - echo "%JL_CODECOV_SCRIPT%" + - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cf776f42..9b007ee7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -26,9 +26,13 @@ jobs: os: - ubuntu-latest - macOS-latest - - windows-latest + # - windows-latest # run on AppVeyor instead arch: - x64 + - x86 + exclude: + - os: macOS-latest + arch: x86 steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 6d2efc1c..ec69bc9d 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -5,6 +5,23 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: 3 + +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: diff --git a/LICENSE.md b/LICENSE.md index e43f902a..f640436b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,22 +1,21 @@ -The TensKit.jl package is licensed under the MIT "Expat" License: +MIT License -> Copyright (c) 2019: Jutho Haegeman. -> -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: -> -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2023 Jutho Haegeman and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.