From 3ef9e85381eae897633b9f3e63e8bbc848255d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 10:53:33 -0400 Subject: [PATCH 01/17] add tensor_product and onetoone --- src/TensorProducts.jl | 5 ++++- src/onetoone.jl | 7 +++++++ src/tensor_product.jl | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/onetoone.jl create mode 100644 src/tensor_product.jl diff --git a/src/TensorProducts.jl b/src/TensorProducts.jl index a774cff..9502aa0 100644 --- a/src/TensorProducts.jl +++ b/src/TensorProducts.jl @@ -1,5 +1,8 @@ module TensorProducts -# Write your package code here. +export ⊗, OneToOne, tensor_product + +include("onetoone.jl") +include("tensor_product.jl") end diff --git a/src/onetoone.jl b/src/onetoone.jl new file mode 100644 index 0000000..ac9c106 --- /dev/null +++ b/src/onetoone.jl @@ -0,0 +1,7 @@ +# This files defines the struct OneToOne +# OneToOne represents the range `1:1` or `Base.OneTo(1)`. + +struct OneToOne{T} <: AbstractUnitRange{T} end +OneToOne() = OneToOne{Bool}() +Base.first(a::OneToOne) = one(eltype(a)) +Base.last(a::OneToOne) = one(eltype(a)) diff --git a/src/tensor_product.jl b/src/tensor_product.jl new file mode 100644 index 0000000..f4f2a5d --- /dev/null +++ b/src/tensor_product.jl @@ -0,0 +1,14 @@ +# This files defines an interface for the tensor product of two axes +# https://en.wikipedia.org/wiki/Tensor_product + +⊗(args...) = tensor_product(args...) + +tensor_product() = OneToOne() + +tensor_product(a1::AbstractUnitRange) = a1 + +function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) + return Base.OneTo(prod(length.((a1, a2)))) +end # default + +tensor_product(a1, a2, as...) = tensor_product(a1, tensor_product(a2, as...)) # no type constraint to accept AbstractSector later From 4a040e05a0bd65fddbeb2ade0ee3ff132f072eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 11:45:41 -0400 Subject: [PATCH 02/17] add tests --- test/basics/test_basics.jl | 6 ------ test/test_basics.jl | 11 +++++++++++ test/test_exports.jl | 8 ++++++++ test/test_tensor_product.jl | 12 ++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) delete mode 100644 test/basics/test_basics.jl create mode 100644 test/test_basics.jl create mode 100644 test/test_exports.jl create mode 100644 test/test_tensor_product.jl diff --git a/test/basics/test_basics.jl b/test/basics/test_basics.jl deleted file mode 100644 index affc163..0000000 --- a/test/basics/test_basics.jl +++ /dev/null @@ -1,6 +0,0 @@ -using TensorProducts: TensorProducts -using Test: @test, @testset - -@testset "TensorProducts" begin - # Tests go here. -end diff --git a/test/test_basics.jl b/test/test_basics.jl new file mode 100644 index 0000000..0199026 --- /dev/null +++ b/test/test_basics.jl @@ -0,0 +1,11 @@ +using Test: @test, @testset + +using TensorProducts: OneToOne + +@testset "OneToOne" begin + a0 = OneToOne() + @test a0 isa OneToOne{Bool} + @test a0 isa AbstractUnitRange{Bool} + @test eltype(a0) == Bool + @test length(a0) == 1 +end diff --git a/test/test_exports.jl b/test/test_exports.jl new file mode 100644 index 0000000..c9349fe --- /dev/null +++ b/test/test_exports.jl @@ -0,0 +1,8 @@ +using Test: @test, @testset + +using TensorProducts: TensorProducts + +@testset "Test exports" begin + exports = [:⊗, :TensorProducts, :OneToOne, :tensor_product] + @test issetequal(names(TensorProducts), exports) +end diff --git a/test/test_tensor_product.jl b/test/test_tensor_product.jl new file mode 100644 index 0000000..0bce0a7 --- /dev/null +++ b/test/test_tensor_product.jl @@ -0,0 +1,12 @@ +using Test: @test, @testset + +using TensorProducts: ⊗, OneToOne, tensor_product + +@testset "tensor_product" begin + @test tensor_product() isa OneToOne{Bool} + @test ⊗() isa OneToOne{Bool} + + g0 = OneToOne() + @test tensor_product(g0, g0) == g0 + @test tensor_product(1:3, 1:2) == 1:6 +end From 2844e5e5c4d9bc161e36197b9ae4156f9375589a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 12:14:40 -0400 Subject: [PATCH 03/17] add BlockArrays extension --- Project.toml | 7 ++++ .../TensorProductsBlockArraysExt.jl | 32 +++++++++++++++++++ test/Project.toml | 2 ++ test/test_basics.jl | 6 ++++ test/test_tensor_product.jl | 5 +++ 5 files changed, 52 insertions(+) create mode 100644 ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl diff --git a/Project.toml b/Project.toml index 1ffcf09..93b675d 100644 --- a/Project.toml +++ b/Project.toml @@ -3,5 +3,12 @@ uuid = "decf83d6-1968-43f4-96dc-fdb3fe15fc6d" authors = ["ITensor developers and contributors"] version = "0.1.0" +[weakdeps] +BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" + +[extensions] +TensorProductsBlockArraysExt = "BlockArrays" + [compat] +BlockArrays = "1.2.0" julia = "1.10" diff --git a/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl new file mode 100644 index 0000000..cb98071 --- /dev/null +++ b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl @@ -0,0 +1,32 @@ +module TensorProductsBlockArraysExt + +using BlockArrays: + AbstractBlockedUnitRange, + Block, + BlockArrays, + blockaxes, + blockedrange, + blocklengths, + blocks + +using TensorProducts: OneToOne, TensorProducts + +# BlockArrays default crashes for OneToOne{Bool} +BlockArrays.blockaxes(a::OneToOne) = (Block.(a),) + +function fuse_blocklengths(x::Integer, y::Integer) + # return blocked unit range to keep non-abelian interface + return blockedrange([x * y]) +end + +function TensorProducts.tensor_product( + a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange +) + nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it + return mapreduce(length, fuse_blocklengths, it) + end + new_blocklengths = mapreduce(blocklengths, vcat, nested) + return blockedrange(new_blocklengths) +end + +end diff --git a/test/Project.toml b/test/Project.toml index e9c291e..026b6b0 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,11 +1,13 @@ [deps] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Aqua = "0.8.9" +BlockArrays = "1.2.0" SafeTestsets = "0.1" Suppressor = "0.2" Test = "1.10" diff --git a/test/test_basics.jl b/test/test_basics.jl index 0199026..900a216 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -9,3 +9,9 @@ using TensorProducts: OneToOne @test eltype(a0) == Bool @test length(a0) == 1 end + +using BlockArrays: BlockRange, blockaxes + +@testset "BlockArray interface OneToOne" begin + @test blockaxes(OneToOne()) == (BlockRange(OneToOne()),) +end diff --git a/test/test_tensor_product.jl b/test/test_tensor_product.jl index 0bce0a7..b238f7d 100644 --- a/test/test_tensor_product.jl +++ b/test/test_tensor_product.jl @@ -2,6 +2,8 @@ using Test: @test, @testset using TensorProducts: ⊗, OneToOne, tensor_product +using BlockArrays: blockedrange, blockisequal + @testset "tensor_product" begin @test tensor_product() isa OneToOne{Bool} @test ⊗() isa OneToOne{Bool} @@ -9,4 +11,7 @@ using TensorProducts: ⊗, OneToOne, tensor_product g0 = OneToOne() @test tensor_product(g0, g0) == g0 @test tensor_product(1:3, 1:2) == 1:6 + + b1 = blockedrange([1, 2]) + @test blockisequal(tensor_product(b1, b1), blockedrange([1, 2, 2, 4])) end From 3dd63d74e2387f9006bb56bd4e4e29e3fd08dce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 12:47:58 -0400 Subject: [PATCH 04/17] add dual interface --- src/TensorProducts.jl | 3 ++- src/dual.jl | 37 ++++++++++++++++++++++++++++++++++ test/test_basics.jl | 46 +++++++++++++++++++++++++++++++++++++++---- test/test_exports.jl | 2 +- 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/dual.jl diff --git a/src/TensorProducts.jl b/src/TensorProducts.jl index 9502aa0..72e1747 100644 --- a/src/TensorProducts.jl +++ b/src/TensorProducts.jl @@ -1,8 +1,9 @@ module TensorProducts -export ⊗, OneToOne, tensor_product +export ⊗, OneToOne, dag, dual, flip, isdual, tensor_product include("onetoone.jl") +include("dual.jl") include("tensor_product.jl") end diff --git a/src/dual.jl b/src/dual.jl new file mode 100644 index 0000000..9116905 --- /dev/null +++ b/src/dual.jl @@ -0,0 +1,37 @@ +""" + dual(x) + +Take the dual of the symmetry sector, graded unit range, etc. +By default, it just returns `x`, i.e. it assumes the object +is self-dual. +""" +dual(x) = x + +nondual(r::AbstractUnitRange) = r +isdual(::AbstractUnitRange) = false + +dual_type(x) = dual_type(typeof(x)) +dual_type(T::Type) = T +nondual_type(x) = nondual_type(typeof(x)) +nondual_type(T::Type) = T + +map_blocklabels(::Any, a::AbstractUnitRange) = a +flip(a::AbstractUnitRange) = dual(map_blocklabels(dual, a)) + +""" + dag(r::AbstractUnitRange) + +Same as `dual(r)`. +""" +dag(r::AbstractUnitRange) = dual(r) + +""" + dag(a::AbstractArray) + +Complex conjugates `a` and takes the dual of the axes. +""" +function dag(a::AbstractArray) + a′ = similar(a, dual.(axes(a))) + a′ .= conj.(a) + return a′ +end diff --git a/test/test_basics.jl b/test/test_basics.jl index 900a216..2fb71da 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,6 +1,8 @@ using Test: @test, @testset -using TensorProducts: OneToOne +using BlockArrays: Block, BlockedOneTo, BlockRange, blockaxes, blockedrange, blockisequal + +using TensorProducts: dag, dual, flip, isdual, OneToOne @testset "OneToOne" begin a0 = OneToOne() @@ -8,10 +10,46 @@ using TensorProducts: OneToOne @test a0 isa AbstractUnitRange{Bool} @test eltype(a0) == Bool @test length(a0) == 1 + + @test blockaxes(OneToOne()) == (BlockRange(OneToOne()),) end -using BlockArrays: BlockRange, blockaxes +@testset "dual" begin + a0 = OneToOne() + @test !isdual(a0) + @test dual(a0) isa OneToOne + + a = 1:3 + ad = dual(a) + af = flip(a) + @test !isdual(a) + @test !isdual(ad) + @test !isdual(dag(a)) + @test !isdual(af) + @test ad isa UnitRange + @test af isa UnitRange + @test blockisequal(ad, a) + @test blockisequal(af, a) -@testset "BlockArray interface OneToOne" begin - @test blockaxes(OneToOne()) == (BlockRange(OneToOne()),) + a = blockedrange([2, 3]) + ad = dual(a) + af = flip(a) + @test !isdual(a) + @test !isdual(ad) + @test ad isa BlockedOneTo + @test af isa BlockedOneTo + @test blockisequal(ad, a) + @test blockisequal(af, a) +end + +@testset "dag" begin + elt = ComplexF64 + r = blockedrange([2, 3]) + a = zeros(elt, r, dual(r)) + a[Block(1, 1)] = randn(elt, 2, 2) + a[Block(2, 2)] = randn(elt, 3, 3) + @test isdual.(axes(a)) == (false, false) + ad = dag(a) + @test Array(ad) == conj(Array(a)) + @test isdual.(axes(ad)) == (false, false) end diff --git a/test/test_exports.jl b/test/test_exports.jl index c9349fe..8a1b000 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -3,6 +3,6 @@ using Test: @test, @testset using TensorProducts: TensorProducts @testset "Test exports" begin - exports = [:⊗, :TensorProducts, :OneToOne, :tensor_product] + exports = [:⊗, :TensorProducts, :OneToOne, :dag, :dual, :flip, :isdual, :tensor_product] @test issetequal(names(TensorProducts), exports) end From f81bfc830e95d2c38ff309a1a07f83d62081d982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 14:04:56 -0400 Subject: [PATCH 05/17] fix formatting --- docs/make.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index b75189f..3d5864c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,9 +1,7 @@ using TensorProducts: TensorProducts using Documenter: Documenter, DocMeta, deploydocs, makedocs -DocMeta.setdocmeta!( - TensorProducts, :DocTestSetup, :(using TensorProducts); recursive=true -) +DocMeta.setdocmeta!(TensorProducts, :DocTestSetup, :(using TensorProducts); recursive=true) include("make_index.jl") From 300ee709dfe5ceb20313399b6e016fe98674d103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 14:43:22 -0400 Subject: [PATCH 06/17] remove dual --- src/TensorProducts.jl | 2 +- src/dual.jl | 37 ------------------------------------ test/test_basics.jl | 44 ++----------------------------------------- test/test_exports.jl | 2 +- 4 files changed, 4 insertions(+), 81 deletions(-) delete mode 100644 src/dual.jl diff --git a/src/TensorProducts.jl b/src/TensorProducts.jl index 72e1747..c714cd6 100644 --- a/src/TensorProducts.jl +++ b/src/TensorProducts.jl @@ -1,6 +1,6 @@ module TensorProducts -export ⊗, OneToOne, dag, dual, flip, isdual, tensor_product +export ⊗, OneToOne, tensor_product include("onetoone.jl") include("dual.jl") diff --git a/src/dual.jl b/src/dual.jl deleted file mode 100644 index 9116905..0000000 --- a/src/dual.jl +++ /dev/null @@ -1,37 +0,0 @@ -""" - dual(x) - -Take the dual of the symmetry sector, graded unit range, etc. -By default, it just returns `x`, i.e. it assumes the object -is self-dual. -""" -dual(x) = x - -nondual(r::AbstractUnitRange) = r -isdual(::AbstractUnitRange) = false - -dual_type(x) = dual_type(typeof(x)) -dual_type(T::Type) = T -nondual_type(x) = nondual_type(typeof(x)) -nondual_type(T::Type) = T - -map_blocklabels(::Any, a::AbstractUnitRange) = a -flip(a::AbstractUnitRange) = dual(map_blocklabels(dual, a)) - -""" - dag(r::AbstractUnitRange) - -Same as `dual(r)`. -""" -dag(r::AbstractUnitRange) = dual(r) - -""" - dag(a::AbstractArray) - -Complex conjugates `a` and takes the dual of the axes. -""" -function dag(a::AbstractArray) - a′ = similar(a, dual.(axes(a))) - a′ .= conj.(a) - return a′ -end diff --git a/test/test_basics.jl b/test/test_basics.jl index 2fb71da..b5aa92b 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,8 +1,8 @@ using Test: @test, @testset -using BlockArrays: Block, BlockedOneTo, BlockRange, blockaxes, blockedrange, blockisequal +using BlockArrays: BlockRange, blockaxes -using TensorProducts: dag, dual, flip, isdual, OneToOne +using TensorProducts: OneToOne @testset "OneToOne" begin a0 = OneToOne() @@ -13,43 +13,3 @@ using TensorProducts: dag, dual, flip, isdual, OneToOne @test blockaxes(OneToOne()) == (BlockRange(OneToOne()),) end - -@testset "dual" begin - a0 = OneToOne() - @test !isdual(a0) - @test dual(a0) isa OneToOne - - a = 1:3 - ad = dual(a) - af = flip(a) - @test !isdual(a) - @test !isdual(ad) - @test !isdual(dag(a)) - @test !isdual(af) - @test ad isa UnitRange - @test af isa UnitRange - @test blockisequal(ad, a) - @test blockisequal(af, a) - - a = blockedrange([2, 3]) - ad = dual(a) - af = flip(a) - @test !isdual(a) - @test !isdual(ad) - @test ad isa BlockedOneTo - @test af isa BlockedOneTo - @test blockisequal(ad, a) - @test blockisequal(af, a) -end - -@testset "dag" begin - elt = ComplexF64 - r = blockedrange([2, 3]) - a = zeros(elt, r, dual(r)) - a[Block(1, 1)] = randn(elt, 2, 2) - a[Block(2, 2)] = randn(elt, 3, 3) - @test isdual.(axes(a)) == (false, false) - ad = dag(a) - @test Array(ad) == conj(Array(a)) - @test isdual.(axes(ad)) == (false, false) -end diff --git a/test/test_exports.jl b/test/test_exports.jl index 8a1b000..c9349fe 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -3,6 +3,6 @@ using Test: @test, @testset using TensorProducts: TensorProducts @testset "Test exports" begin - exports = [:⊗, :TensorProducts, :OneToOne, :dag, :dual, :flip, :isdual, :tensor_product] + exports = [:⊗, :TensorProducts, :OneToOne, :tensor_product] @test issetequal(names(TensorProducts), exports) end From bb8fd8230c22a0911e63c7560354494ce41862e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 14:55:45 -0400 Subject: [PATCH 07/17] more tests --- src/TensorProducts.jl | 1 - test/test_tensor_product.jl | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/TensorProducts.jl b/src/TensorProducts.jl index c714cd6..9502aa0 100644 --- a/src/TensorProducts.jl +++ b/src/TensorProducts.jl @@ -3,7 +3,6 @@ module TensorProducts export ⊗, OneToOne, tensor_product include("onetoone.jl") -include("dual.jl") include("tensor_product.jl") end diff --git a/test/test_tensor_product.jl b/test/test_tensor_product.jl index b238f7d..ca21f3c 100644 --- a/test/test_tensor_product.jl +++ b/test/test_tensor_product.jl @@ -5,13 +5,18 @@ using TensorProducts: ⊗, OneToOne, tensor_product using BlockArrays: blockedrange, blockisequal @testset "tensor_product" begin + g0 = OneToOne() + @test tensor_product() isa OneToOne{Bool} @test ⊗() isa OneToOne{Bool} - g0 = OneToOne() + @test tensor_product(1:2) == 1:2 + @test tensor_product(g0, g0) == g0 @test tensor_product(1:3, 1:2) == 1:6 b1 = blockedrange([1, 2]) @test blockisequal(tensor_product(b1, b1), blockedrange([1, 2, 2, 4])) + + @test tensor_product(1:3, 1:2, 1:2) == 1:12 end From 5782c9124d0316738b38bf8ed9da82a216dec81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 16:06:20 -0400 Subject: [PATCH 08/17] =?UTF-8?q?allow=20to=20specialize=20=E2=8A=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tensor_product.jl | 15 +++++++++------ test/test_tensor_product.jl | 26 ++++++++++++++++---------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index f4f2a5d..e2d3520 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -1,14 +1,17 @@ # This files defines an interface for the tensor product of two axes # https://en.wikipedia.org/wiki/Tensor_product -⊗(args...) = tensor_product(args...) +⊗() = tensor_product() +⊗(a) = tensor_product(a) +⊗(a1, a2) = tensor_product(a1, a2) # default +⊗(a1, a2, as...) = ⊗(⊗(a1, a2), as...) # allow to specialize ⊗(a1, a2) to fusion_product tensor_product() = OneToOne() +tensor_product(a) = a +tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) -tensor_product(a1::AbstractUnitRange) = a1 - -function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) +function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) # default return Base.OneTo(prod(length.((a1, a2)))) -end # default +end -tensor_product(a1, a2, as...) = tensor_product(a1, tensor_product(a2, as...)) # no type constraint to accept AbstractSector later +tensor_product(::OneToOne, ::OneToOne) = OneToOne() diff --git a/test/test_tensor_product.jl b/test/test_tensor_product.jl index ca21f3c..5cea11a 100644 --- a/test/test_tensor_product.jl +++ b/test/test_tensor_product.jl @@ -4,19 +4,25 @@ using TensorProducts: ⊗, OneToOne, tensor_product using BlockArrays: blockedrange, blockisequal -@testset "tensor_product" begin - g0 = OneToOne() +r0 = OneToOne() +b1 = blockedrange([1, 2]) - @test tensor_product() isa OneToOne{Bool} - @test ⊗() isa OneToOne{Bool} +@testset "⊗" begin + @test ⊗() isa OneToOne + @test ⊗(1:2) == 1:2 + @test ⊗(1:2, 1:3) == 1:6 + @test ⊗(1:2, 1:3, 1:4) == 1:24 - @test tensor_product(1:2) == 1:2 + @test ⊗(r0, r0) isa OneToOne + @test blockisequal(⊗(b1, b1), blockedrange([1, 2, 2, 4])) +end - @test tensor_product(g0, g0) == g0 - @test tensor_product(1:3, 1:2) == 1:6 +@testset "tensor_product" begin + @test tensor_product() isa OneToOne + @test tensor_product(1:2) == 1:2 + @test tensor_product(1:2, 1:3) == 1:6 + @test tensor_product(1:2, 1:3, 1:4) == 1:24 - b1 = blockedrange([1, 2]) + @test tensor_product(r0, r0) isa OneToOne @test blockisequal(tensor_product(b1, b1), blockedrange([1, 2, 2, 4])) - - @test tensor_product(1:3, 1:2, 1:2) == 1:12 end From cbbab4cf4f7e4296cd7baaae48bff523e5b6dbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 17:09:39 -0400 Subject: [PATCH 09/17] errors for non one-based --- src/tensor_product.jl | 2 ++ test/test_tensor_product.jl | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index e2d3520..5249448 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -11,6 +11,8 @@ tensor_product(a) = a tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) # default + !(isone(first(a1)) && isone(first(a2))) && + throw(ArgumentError("Ranges must be one-based")) return Base.OneTo(prod(length.((a1, a2)))) end diff --git a/test/test_tensor_product.jl b/test/test_tensor_product.jl index 5cea11a..2a173aa 100644 --- a/test/test_tensor_product.jl +++ b/test/test_tensor_product.jl @@ -1,4 +1,4 @@ -using Test: @test, @testset +using Test: @test, @test_throws, @testset using TensorProducts: ⊗, OneToOne, tensor_product @@ -23,6 +23,10 @@ end @test tensor_product(1:2, 1:3) == 1:6 @test tensor_product(1:2, 1:3, 1:4) == 1:24 + @test_throws ArgumentError tensor_product(2:3, 1:2) + @test_throws ArgumentError tensor_product(1:3, 2:2) + @test_throws ArgumentError tensor_product(2:3, 2:2) + @test tensor_product(r0, r0) isa OneToOne @test blockisequal(tensor_product(b1, b1), blockedrange([1, 2, 2, 4])) end From a407dabdd902721532f63ba91434f69d42c56950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Mon, 24 Mar 2025 17:38:25 -0400 Subject: [PATCH 10/17] move comments --- src/tensor_product.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index 5249448..54b3482 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -3,14 +3,19 @@ ⊗() = tensor_product() ⊗(a) = tensor_product(a) -⊗(a1, a2) = tensor_product(a1, a2) # default -⊗(a1, a2, as...) = ⊗(⊗(a1, a2), as...) # allow to specialize ⊗(a1, a2) to fusion_product + +# default. No type restriction to allow sectors as input +⊗(a1, a2) = tensor_product(a1, a2) + +# allow to specialize ⊗(a1, a2) to fusion_product +⊗(a1, a2, as...) = ⊗(⊗(a1, a2), as...) tensor_product() = OneToOne() -tensor_product(a) = a +tensor_product(a::AbstractUnitRange) = a tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) -function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) # default +# default +function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) !(isone(first(a1)) && isone(first(a2))) && throw(ArgumentError("Ranges must be one-based")) return Base.OneTo(prod(length.((a1, a2)))) From c4c1787635d55553021e7884a0d27b90f9b79f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 25 Mar 2025 10:48:39 -0400 Subject: [PATCH 11/17] default tensor_product(a) --- src/tensor_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index 54b3482..1c67bda 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -11,7 +11,7 @@ ⊗(a1, a2, as...) = ⊗(⊗(a1, a2), as...) tensor_product() = OneToOne() -tensor_product(a::AbstractUnitRange) = a +tensor_product(a) = a tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) # default From 35b269478bd77e07c3b27b23e846175a0cbdfb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 25 Mar 2025 14:00:33 -0400 Subject: [PATCH 12/17] simpler syntax --- src/tensor_product.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index 1c67bda..000bb9d 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -18,7 +18,7 @@ tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) !(isone(first(a1)) && isone(first(a2))) && throw(ArgumentError("Ranges must be one-based")) - return Base.OneTo(prod(length.((a1, a2)))) + return Base.OneTo(length(a1) * length(a2)) end tensor_product(::OneToOne, ::OneToOne) = OneToOne() From dec5a3701b4193188c8b4f9d27995dea31f1cad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 25 Mar 2025 16:29:38 -0400 Subject: [PATCH 13/17] helper functions --- src/tensor_product.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index 000bb9d..3bf8850 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -1,6 +1,14 @@ # This files defines an interface for the tensor product of two axes # https://en.wikipedia.org/wiki/Tensor_product +# ================================== misc ================================================ +is_offset_axis(a::AbstractUnitRange) = isone(first(a)) + +function require_one_based_axis(a::AbstractUnitRange) + return !is_offset_axis(a) && throw(ArgumentError("Range must be one-based")) +end + +# ============================== tensor product ========================================== ⊗() = tensor_product() ⊗(a) = tensor_product(a) @@ -16,8 +24,7 @@ tensor_product(a1, a2, as...) = tensor_product(tensor_product(a1, a2), as...) # default function tensor_product(a1::AbstractUnitRange, a2::AbstractUnitRange) - !(isone(first(a1)) && isone(first(a2))) && - throw(ArgumentError("Ranges must be one-based")) + require_one_based_axis(a1) || require_one_based_axis(a2) return Base.OneTo(length(a1) * length(a2)) end From 4b1881a25ff8add4dbf8bb107bc53f18a5b7d899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Tue, 25 Mar 2025 19:21:01 -0400 Subject: [PATCH 14/17] fix is_offset_axis --- src/tensor_product.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tensor_product.jl b/src/tensor_product.jl index 3bf8850..35a7d32 100644 --- a/src/tensor_product.jl +++ b/src/tensor_product.jl @@ -2,10 +2,10 @@ # https://en.wikipedia.org/wiki/Tensor_product # ================================== misc ================================================ -is_offset_axis(a::AbstractUnitRange) = isone(first(a)) +is_offset_axis(a::AbstractUnitRange) = !isone(first(a)) function require_one_based_axis(a::AbstractUnitRange) - return !is_offset_axis(a) && throw(ArgumentError("Range must be one-based")) + return is_offset_axis(a) && throw(ArgumentError("Range must be one-based")) end # ============================== tensor product ========================================== From 50d1a1993c5c0185ab79836569dadefcb0b3f36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 26 Mar 2025 09:23:14 -0400 Subject: [PATCH 15/17] remove fuse_blocklengths --- .../TensorProductsBlockArraysExt.jl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl index cb98071..a284091 100644 --- a/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl +++ b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl @@ -14,18 +14,12 @@ using TensorProducts: OneToOne, TensorProducts # BlockArrays default crashes for OneToOne{Bool} BlockArrays.blockaxes(a::OneToOne) = (Block.(a),) -function fuse_blocklengths(x::Integer, y::Integer) - # return blocked unit range to keep non-abelian interface - return blockedrange([x * y]) -end - function TensorProducts.tensor_product( a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange ) - nested = map(Iterators.flatten((Iterators.product(blocks(a1), blocks(a2)),))) do it - return mapreduce(length, fuse_blocklengths, it) + new_blocklengths = mapreduce(vcat, Iterators.product(blocks(a1), blocks(a2))) do (x, y) + return length(x) * length(y) end - new_blocklengths = mapreduce(blocklengths, vcat, nested) return blockedrange(new_blocklengths) end From 0a16571f7dfab605709acd06e2ae2be0e2952874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 26 Mar 2025 09:32:00 -0400 Subject: [PATCH 16/17] OneToOne with Int eltype --- .../TensorProductsBlockArraysExt.jl | 3 --- src/onetoone.jl | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl index a284091..f6b71e7 100644 --- a/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl +++ b/ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl @@ -11,9 +11,6 @@ using BlockArrays: using TensorProducts: OneToOne, TensorProducts -# BlockArrays default crashes for OneToOne{Bool} -BlockArrays.blockaxes(a::OneToOne) = (Block.(a),) - function TensorProducts.tensor_product( a1::AbstractBlockedUnitRange, a2::AbstractBlockedUnitRange ) diff --git a/src/onetoone.jl b/src/onetoone.jl index ac9c106..a3bda64 100644 --- a/src/onetoone.jl +++ b/src/onetoone.jl @@ -2,6 +2,6 @@ # OneToOne represents the range `1:1` or `Base.OneTo(1)`. struct OneToOne{T} <: AbstractUnitRange{T} end -OneToOne() = OneToOne{Bool}() +OneToOne() = OneToOne{Int}() Base.first(a::OneToOne) = one(eltype(a)) Base.last(a::OneToOne) = one(eltype(a)) From a9f9bc6a4c0e45419bd93711d7ca513c3b7ebfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Gauth=C3=A9?= Date: Wed, 26 Mar 2025 09:34:40 -0400 Subject: [PATCH 17/17] fix tests --- test/test_basics.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_basics.jl b/test/test_basics.jl index b5aa92b..745a2e8 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -6,9 +6,9 @@ using TensorProducts: OneToOne @testset "OneToOne" begin a0 = OneToOne() - @test a0 isa OneToOne{Bool} - @test a0 isa AbstractUnitRange{Bool} - @test eltype(a0) == Bool + @test a0 isa OneToOne{Int} + @test a0 isa AbstractUnitRange{Int} + @test eltype(a0) == Int @test length(a0) == 1 @test blockaxes(OneToOne()) == (BlockRange(OneToOne()),)