diff --git a/experimental/SetPartitions/README.md b/experimental/SetPartitions/README.md index a0c28c6b79cf..0069314df1f6 100644 --- a/experimental/SetPartitions/README.md +++ b/experimental/SetPartitions/README.md @@ -13,10 +13,7 @@ This includes: * basic set-partitions and operations on them (e.g. composition, tensor product, involution) * variations like colored partitions [TW18](@cite) and spatial partitions [CW16](@cite) * enumeration of partitions which can be constructed from a set of generators - -In the future, we plan to implement: * linear combinations of partitions -* examples of tensor categories in the framework of [TensorCategories.jl](https://github.com/FabianMaeurer/TensorCategories.jl) ## Contact diff --git a/experimental/SetPartitions/src/LinearPartition.jl b/experimental/SetPartitions/src/LinearPartition.jl new file mode 100644 index 000000000000..7fb05774ff54 --- /dev/null +++ b/experimental/SetPartitions/src/LinearPartition.jl @@ -0,0 +1,210 @@ +""" + LinearPartition{S <: AbstractPartition, T <: RingElem} + +`LinearPartition` represents a linear combination of partitions of type `S` +with coefficients of type `T`. + +See for example Chapter 5 in [Gro20](@cite) for further information on linear +combinations of partitions. +""" +struct LinearPartition{S <: AbstractPartition, T <: RingElem} + base_ring :: Ring # parent_type(T) + coefficients :: Dict{S, T} + + function LinearPartition{S, T}(ring::Ring, coeffs::Dict{S, T}) where {S <: AbstractPartition, T <: RingElem} + @req isconcretetype(S) "Linear combinations are only defined for concrete subtypes of AbstractPartition" + @req ring isa parent_type(T) "Ring type is not the parent of the coefficient type" + for c in values(coeffs) + @req (parent(c) == ring) "Coefficient does not belong to the base ring" + end + return new(ring, simplify_operation_zero(coeffs)) + end +end + +""" + linear_partition(ring::Ring, coeffs::Dict{S, <: Any}) where {S <: AbstractPartition} + +Construct a linear partition over `ring` with coefficients `coeffs`. + +# Examples +```jldoctest +julia> S, d = polynomial_ring(QQ, "d") +(Univariate polynomial ring in d over QQ, d) + +julia> linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 4, set_partition([1, 1], [1, 1]) => 4*d)) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 2], [1, 1]) => 4, SetPartition([1, 1], [1, 1]) => 4*d)) +``` +""" +function linear_partition(ring::Ring, coeffs::Dict{S, <: Any}) where {S <: AbstractPartition} + ring_coeffs = Dict(p => ring(c) for (p, c) in coeffs) + return LinearPartition{S, elem_type(ring)}(ring, ring_coeffs) +end + +""" + linear_partition(ring::Ring, terms::Vector{Tuple{S, <: Any}}) where {S <: AbstractPartition} + +Return a `LinearPartition` generated from the vector `term` of 2-tuples, +where the first element in the tuple is an `AbstractPartition` and the second +a `RingElem`. The `ring` argument defines the base ring into which the second +elements of the tuples are converted. Furthermore simplify the term before initializing +the `LinearPartition` object with the corresponding dict. + +# Examples +```jldoctest +julia> S, d = polynomial_ring(QQ, "d") +(Univariate polynomial ring in d over QQ, d) + +julia> linear_partition(S, [(set_partition([1, 1], [1, 1]), 4), (set_partition([1, 1], [1, 1]), 4*d)]) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 1], [1, 1]) => 4*d + 4)) +``` +""" +function linear_partition(ring::Ring, terms::Vector{Tuple{S, <: Any}}) where {S <: AbstractPartition} + simpl_terms = simplify_operation([(p, ring(c)) for (p, c) in terms]) + return linear_partition(ring, Dict{S, elem_type(ring)}(simpl_terms)) +end + +""" + base_ring(p::LinearPartition{S, T}) where {S <: AbstractPartition, T <: RingElem} + +Return the underlying coefficient ring of `p`. +""" +function base_ring(p::LinearPartition{S, T}) where {S <: AbstractPartition, T <: RingElem} + return p.base_ring::parent_type(T) +end + +function base_ring_type(::Type{LinearPartition{S, T}}) where {S <: AbstractPartition, T <: RingElem} + return parent_type(T) +end + +""" + coefficients(p::LinearPartition) + +Return the coefficients of `p` as dictionary from partitions to elements +of the underlying ring. +""" +function coefficients(p::LinearPartition) + return p.coefficients +end + +function hash(p::LinearPartition, h::UInt) + return hash(base_ring(p), hash(coefficients(p), h)) +end + +function ==(p::LinearPartition, q::LinearPartition) + return base_ring(p) == base_ring(q) && coefficients(p) == coefficients(q) +end + +function deepcopy_internal(p::LinearPartition, stackdict::IdDict) + if haskey(stackdict, p) + return stackdict[p] + end + q = linear_partition( + base_ring(p), + deepcopy_internal(coefficients(p), stackdict)) + stackdict[p] = q + return q +end + +function +(p::LinearPartition{S, T}, q::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + @req base_ring(p) == base_ring(q) "Linear partitions are defined over different base rings" + result = deepcopy(coefficients(p)) + for i in pairs(coefficients(q)) + result[i[1]] = get(result, i[1], 0) + i[2] + end + return linear_partition(base_ring(p), result) +end + +function *(a::RingElement, p::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + a = base_ring(p)(a) + result = Dict{S, T}() + for (i, n) in pairs(coefficients(p)) + result[i] = a * n + end + return linear_partition(base_ring(p), result) +end + +""" + compose(p::LinearPartition{S, T}, q::LinearPartition{S, T}, d::T) where + { S <: AbstractPartition, T <: RingElem } + +Return the composition between `p` and `q`. + +The composition is obtained by multiplying each coefficient +of `p` with each coefficient of `q` and composing the corresponding +partitions. The `RingElem` parameter `d` multiplies each +coefficient based on the number of loops identified during the +composition. + +# Examples +```jldoctest +julia> S, d = polynomial_ring(QQ, "d") +(Univariate polynomial ring in d over QQ, d) + +julia> a = linear_partition(S, [(set_partition([1, 2], [1, 1]), 4), (set_partition([1, 1], [1, 1]), 4*d)]) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 2], [1, 1]) => 4, SetPartition([1, 1], [1, 1]) => 4*d)) + +julia> compose(a, a, d) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 2], [1, 1]) => 16*d + 16, SetPartition([1, 1], [1, 1]) => 16*d^2 + 16*d)) +``` +""" +function compose(p::LinearPartition{S, T}, q::LinearPartition{S, T}, d::T) where + { S <: AbstractPartition, T <: RingElem } + @req base_ring(p) == base_ring(q) "Linear partitions are defined over different base rings" + result = Dict{S, T}() + for i in pairs(coefficients(p)) + for ii in pairs(coefficients(q)) + (composition, loop) = compose_count_loops(i[1], ii[1]) + new_coefficient = i[2] * ii[2] * (d^loop) + result[composition] = get(result, composition, 0) + new_coefficient + end + end + return linear_partition(base_ring(p), result) +end + +""" + tensor_product(p::LinearPartition{S, T}, q::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + +Return the tensor product of `p` and `q`. + +# Examples +```jldoctest +julia> S, d = polynomial_ring(QQ, "d") +(Univariate polynomial ring in d over QQ, d) + +julia> a = linear_partition(S, [(set_partition([1, 2], [1, 1]), 4), (set_partition([1, 1], [1, 1]), 4*d)]) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 2], [1, 1]) => 4, SetPartition([1, 1], [1, 1]) => 4*d)) + +julia> tensor_product(a, a) +LinearPartition{SetPartition, QQPolyRingElem}(Univariate polynomial ring in d over QQ, Dict{SetPartition, QQPolyRingElem}(SetPartition([1, 1, 2, 2], [1, 1, 2, 2]) => 16*d^2, SetPartition([1, 2, 3, 3], [1, 1, 3, 3]) => 16*d, SetPartition([1, 2, 3, 4], [1, 1, 3, 3]) => 16, SetPartition([1, 1, 2, 3], [1, 1, 2, 2]) => 16*d)) +``` +""" +function tensor_product(p::LinearPartition{S, T}, q::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + @req base_ring(p) == base_ring(q) "Linear partitions are defined over different base rings" + result = Dict{S, T}() + for i in pairs(coefficients(p)) + for ii in pairs(coefficients(q)) + composition = tensor_product(i[1], ii[1]) + new_coefficient = i[2] * ii[2] + result[composition] = get(result, composition, 0) + new_coefficient + end + end + return linear_partition(base_ring(p), result) +end + +function ⊗(p::LinearPartition{S, T}, q::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + return tensor_product(p, q) +end + +function -(p::LinearPartition) + return (-1 * p) +end + +function -(p::LinearPartition{S, T}, q::LinearPartition{S, T}) where + { S <: AbstractPartition, T <: RingElem } + return p + (-q) +end diff --git a/experimental/SetPartitions/src/SetPartitions.jl b/experimental/SetPartitions/src/SetPartitions.jl index 4763ded1a157..b55f852adbf3 100644 --- a/experimental/SetPartitions/src/SetPartitions.jl +++ b/experimental/SetPartitions/src/SetPartitions.jl @@ -1,8 +1,10 @@ module SetPartitions import Base: + +, + -, + *, ==, - *, adjoint, deepcopy, deepcopy_internal, @@ -10,20 +12,30 @@ import Base: size import Oscar: + PermGroupElem, + Ring, + RingElem, + RingElement, ⊗, + @req, + base_ring, + base_ring_type, + coefficients, compose, cycles, + degree, + elem_type, involution, + iszero, join, - PermGroupElem, parent, - degree, - tensor_product, - @req + parent_type, + tensor_product export ColoredPartition export SetPartition export SpatialPartition +export LinearPartition export colored_partition export compose_count_loops @@ -36,6 +48,7 @@ export is_non_crossing export is_pair export join export levels +export linear_partition export lower_colors export lower_points export number_of_blocks @@ -53,6 +66,7 @@ export upper_colors export upper_points + include("AbstractPartition.jl") include("Util.jl") include("SetPartition.jl") @@ -60,6 +74,7 @@ include("ColoredPartition.jl") include("SpatialPartition.jl") include("PartitionProperties.jl") include("GenerateCategory.jl") +include("LinearPartition.jl") end using .SetPartitions @@ -67,6 +82,7 @@ using .SetPartitions export ColoredPartition export SetPartition export SpatialPartition +export LinearPartition export colored_partition export compose_count_loops @@ -79,6 +95,7 @@ export is_non_crossing export is_pair export join export levels +export linear_partition export lower_colors export lower_points export number_of_blocks diff --git a/experimental/SetPartitions/src/Util.jl b/experimental/SetPartitions/src/Util.jl index e8a17e6b4f16..869c37e5fba0 100644 --- a/experimental/SetPartitions/src/Util.jl +++ b/experimental/SetPartitions/src/Util.jl @@ -123,3 +123,47 @@ function _add_partition_top_bottom(vector::Vector{Dict{Int, Set{T}}}, p::T) wher return vector end + +""" + simplify_operation(partition_sum::Vector{Tuple{S, T}}) where { S <: AbstractPartition, T <: RingElement } + +Simplify the vector representation of a `LinearPartition` in terms of distributivity. + +# Examples +```jldoctest +julia> S, d = polynomial_ring(QQ, "d") +(Univariate polynomial ring in d over QQ, d) + +julia> Oscar.SetPartitions.simplify_operation([(set_partition([1, 1], [1, 1]), S(10)), (set_partition([1, 1], [1, 1]), 4*d)]) +1-element Vector{Tuple{SetPartition, QQPolyRingElem}}: + (SetPartition([1, 1], [1, 1]), 4*d + 10) +``` +""" +function simplify_operation(partition_sum::Vector{Tuple{S, T}}) where { S <: AbstractPartition, T <: RingElement } + + partitions = Dict{S, T}() + + for (i1, i2) in partition_sum + if iszero(i2) + continue + end + partitions[i1] = get(partitions, i1, 0) + i2 + end + + return [(s, t) for (s, t) in partitions if !iszero(t)] +end + +""" + simplify_operation_zero(p::Dict{S, T}) where { S <: AbstractPartition, T <: RingElement } + +Simplify the dict representation of a `LinearPartition` in terms of zero coefficients. +""" +function simplify_operation_zero(p::Dict{S, T}) where { S <: AbstractPartition, T <: RingElement } + result = Dict{S, T}() + for (i1, i2) in pairs(p) + if !iszero(i2) + result[i1] = i2 + end + end + return result +end diff --git a/experimental/SetPartitions/test/LinearPartition-test.jl b/experimental/SetPartitions/test/LinearPartition-test.jl new file mode 100644 index 000000000000..5c7e857ce350 --- /dev/null +++ b/experimental/SetPartitions/test/LinearPartition-test.jl @@ -0,0 +1,27 @@ +@testset "LinearCombinations of SetPartitions" begin + @testset "LinearPartition Constructor" begin + S, d = polynomial_ring(QQ, "d") + @test coefficients(linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 4, set_partition([1, 1], [1, 1]) => 8*d))) == Dict(set_partition([1, 2], [1, 1]) => 4, set_partition([1, 1], [1, 1]) => 8*d) + @test coefficients(linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 0, set_partition([1, 1], [1, 1]) => 8*d))) == Dict(set_partition([1, 1], [1, 1]) => 8*d) + @test coefficients(linear_partition(S, [(set_partition([1, 1], [1, 1]), 5), (set_partition([1, 1], [1, 1]), 4*d)])) == Dict(set_partition([1, 1], [1, 1]) => 5 + 4*d) + @test coefficients(linear_partition(S, [(set_partition([1, 1], [1, 1]), 10), (set_partition([1, 1], [1, 1]), 4*d), (set_partition([1, 1], [1, 2]), 4*d), (set_partition([1, 1], [1, 1]), S(0))])) == Dict(set_partition([1, 1], [1, 2]) => 4*d, set_partition([1, 1], [1, 1]) => 4*d + 10) + @test_throws ArgumentError linear_partition(S, [(spatial_partition([2, 4], [4, 99], 2), 4), (set_partition([1, 1], [1, 1]), 4*d)]) + end + + @testset "LinearPartition Operations" begin + S, d = polynomial_ring(QQ, "d") + a = linear_partition(S, [(set_partition([1, 2], [1, 1]), 5), (set_partition([1, 1], [1, 1]), 5*d)]) + @test a + a == linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 10, set_partition([1, 1], [1, 1]) => 10*d)) + @test a + linear_partition(S, [(set_partition([1, 1], [1, 1]), 1), (set_partition([1, 1], [1, 1]), 2*d)]) == linear_partition(S, [(set_partition([1, 2], [1, 1]), 5), (set_partition([1, 1], [1, 1]), 7*d + 1)]) + + @test 2 * linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 10, set_partition([1, 1], [1, 1]) => 8*d)) == linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 20, set_partition([1, 1], [1, 1]) => 16*d)) + @test (1 // 2) * linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 8, set_partition([1, 1], [1, 1]) => 8*d)) == linear_partition(S, Dict(SetPartition([1, 2], [1, 1]) => 4, SetPartition([1, 1], [1, 1]) => 4*d)) + + a = linear_partition(S, [(set_partition([1, 2], [1, 1]), 4), (set_partition([1, 1], [1, 1]), 4*d)]) + @test compose(a, a, d) == linear_partition(S, Dict(set_partition([1, 2], [1, 1]) => 16*d + 16, set_partition([1, 1], [1, 1]) => 16*d^2 + 16*d)) + + @test tensor_product(a, a) == linear_partition(S, Dict(set_partition([1, 1, 2, 2], [1, 1, 2, 2]) => 16*d^2, set_partition([1, 2, 3, 3], [1, 1, 3, 3]) => 16*d, set_partition([1, 2, 3, 4], [1, 1, 3, 3]) => 16, set_partition([1, 1, 2, 3], [1, 1, 2, 2]) => 16*d)) + + @test a - a == linear_partition(S, Dict(set_partition([1, 1], [1, 1]) => 0)) + end +end