Skip to content
Merged
7 changes: 7 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@ uuid = "decf83d6-1968-43f4-96dc-fdb3fe15fc6d"
authors = ["ITensor developers <[email protected]> and contributors"]
version = "0.1.0"

[weakdeps]
BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e"

[extensions]
TensorProductsBlockArraysExt = "BlockArrays"

[compat]
BlockArrays = "1.2.0"
julia = "1.10"
4 changes: 1 addition & 3 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -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")

Expand Down
32 changes: 32 additions & 0 deletions ext/TensorProductsBlockArraysExt/TensorProductsBlockArraysExt.jl
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion src/TensorProducts.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module TensorProducts

# Write your package code here.
export ⊗, OneToOne, tensor_product

include("onetoone.jl")
include("tensor_product.jl")

end
7 changes: 7 additions & 0 deletions src/onetoone.jl
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this obvious that we want this to be a separate type? Why not just use Base.OneTo(1)?
I wouldn't expect this to be performance critical, and this is simply more work for the compiler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think a dedicated OneToOne can be useful as a dummy axis for the tensor product of zero axis.

OneToOne() = OneToOne{Bool}()
Base.first(a::OneToOne) = one(eltype(a))
Base.last(a::OneToOne) = one(eltype(a))
24 changes: 24 additions & 0 deletions src/tensor_product.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This files defines an interface for the tensor product of two axes
# https://en.wikipedia.org/wiki/Tensor_product

⊗() = tensor_product()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could also be an alias:

Suggested change
() = tensor_product()
const = tensor_product

Then you could define methods for either, instead of both

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not compatible with being either tensor_product or fusion_product depending on the input.

⊗(a) = tensor_product(a)

# 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(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"))
return Base.OneTo(prod(length.((a1, a2))))
end

tensor_product(::OneToOne, ::OneToOne) = OneToOne()
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 0 additions & 6 deletions test/basics/test_basics.jl

This file was deleted.

15 changes: 15 additions & 0 deletions test/test_basics.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Test: @test, @testset

using BlockArrays: BlockRange, blockaxes

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

@test blockaxes(OneToOne()) == (BlockRange(OneToOne()),)
end
8 changes: 8 additions & 0 deletions test/test_exports.jl
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions test/test_tensor_product.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Test: @test, @test_throws, @testset

using TensorProducts: ⊗, OneToOne, tensor_product

using BlockArrays: blockedrange, blockisequal

r0 = OneToOne()
b1 = blockedrange([1, 2])

@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 ⊗(r0, r0) isa OneToOne
@test blockisequal(⊗(b1, b1), blockedrange([1, 2, 2, 4]))
end

@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

@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
Loading