From 5e5a88b450de806a55a8186392b7feb59f895bfc Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 13:44:32 -0500 Subject: [PATCH 1/9] save --- Project.toml | 3 ++- src/arithematics.jl | 53 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 41daf8ed..76b361cd 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["GiggleLiu and contributors"] version = "0.2.1" [deps] +AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Cairo = "159f3aea-2a34-519c-b102-8c37f9878175" Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b" @@ -35,9 +36,9 @@ Polynomials = "2.0" Primes = "0.5" Requires = "1" SIMDTypes = "0.1" +StatsBase = "0.33" TropicalNumbers = "0.4, 0.5" Viznet = "0.3" -StatsBase = "0.33" julia = "1" [extras] diff --git a/src/arithematics.jl b/src/arithematics.jl index 92a59d2f..4d248567 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -2,6 +2,7 @@ using Polynomials: Polynomial using TropicalNumbers: Tropical, CountingTropical using Mods, Primes using Base.Cartesian +import AbstractTrees # pirate Base.abs(x::Mod) = x @@ -275,6 +276,53 @@ Base.one(::Type{ConfigSampler{N,S,C}}) where {N,S,C} = ConfigSampler{N,S,C}(zero Base.zero(::ConfigSampler{N,S,C}) where {N,S,C} = zero(ConfigSampler{N,S,C}) Base.one(::ConfigSampler{N,S,C}) where {N,S,C} = one(ConfigSampler{N,S,C}) +# tree config enumerator +export TreeConfigEnumerator +struct TreeConfigEnumerator{N,S,C} + siblings::Vector{TreeConfigEnumerator{N,S,C}} + isleaf::Bool + data::StaticElementVector{N,S,C} + TreeConfigEnumerator(v::Vector{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = new{N,S,C}(v, false) + TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} = new{N,S,C}(StaticElementVector{N,S,C}[], true, data) +end + +isleaf(t::TreeConfigEnumerator) = t.isleaf +AbstractTrees.children(t::TreeConfigEnumerator) = t.siblings +AbstractTrees.printnode(io::IO, t::TreeConfigEnumerator) = isleaf(t) ? print(io, t.data) : print(io, "○") + +Base.length(x::TreeConfigEnumerator{N}) where N = isleaf(x) ? 1 : prod(length, x.siblings) +Base.eltype(::Type{<:AbstractTrees.TreeIterator{TreeConfigEnumerator{N,S,C}}}) where {N,S,C} = StaticElementVector{N,S,C} +Base.IteratorEltype(::Type{<:AbstractTrees.TreeIterator{TreeConfigEnumerator{N, S, C}}}) where {N,S,C} = Base.HasEltype()a +function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} + if isleaf(x) && isleaf(y) + return x.data == y.data + elseif !isleaf(x) && !isleaf(y) && length(x.siblings) == length(y.siblings) + return all(i->x.siblings[i] == y.siblings[i], lengh(x.siblings)) + else + return false + end +end + +function Base.:+(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} + iszero(x) && return y + iszero(y) && return x + return TreeConfigEnumerator(vcat(x.siblings, y.siblings)) +end + +function Base.:*(x::TreeConfigEnumerator{L,S,C}, y::TreeConfigEnumerator{L,S,C}) where {L,S,C} + iszero(x) && return x + iszero(y) && return y + return TreeConfigEnumerator([x, y]) +end + +Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(TreeConfigEnumerator{N,S,C}[]) +Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator([TreeConfigEnumerator(zero(StaticElementVector{N,S,C}))]) +Base.zero(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = zero(TreeConfigEnumerator{N,S,C}) +Base.one(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = one(TreeConfigEnumerator{N,S,C}) +# todo, check siblings too? +Base.iszero(t::TreeConfigEnumerator) = isempty(t.siblings) +Base.iterate(t::TreeConfigEnumerator) = iterate(AbstractTrees.PreOrderDFS(t)) + # A patch to make `Polynomial{ConfigEnumerator}` work function Base.:*(a::Int, y::ConfigEnumerator) a == 0 && return zero(y) @@ -312,12 +360,15 @@ end # utilities for creating onehot vectors onehotv(::Type{ConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigEnumerator([onehotv(StaticElementVector{N,S,C}, i, v)]) +onehotv(::Type{TreeConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = TreeConfigEnumerator([TreeConfigEnumerator(onehotv(StaticElementVector{N,S,C}, i, v))]) onehotv(::Type{ConfigSampler{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigSampler(onehotv(StaticElementVector{N,S,C}, i, v)) Base.transpose(c::ConfigEnumerator) = c Base.copy(c::ConfigEnumerator) = ConfigEnumerator(copy(c.data)) +Base.transpose(c::TreeConfigEnumerator) = c +Base.copy(c::TreeConfigEnumerator) = isleaf(c) ? TreeConfigEnumerator(c.data) : TreeConfigEnumerator(c.siblings) # Handle boolean, this is a patch for CUDA matmul -for TYPE in [:ConfigEnumerator, :ConfigSampler, :TruncatedPoly] +for TYPE in [:ConfigEnumerator, :ConfigSampler, :TruncatedPoly, :TreeConfigEnumerator] @eval Base.:*(a::Bool, y::$TYPE) = a ? y : zero(y) @eval Base.:*(y::$TYPE, a::Bool) = a ? y : zero(y) end From 7154a0a293753900f7ddc144636b3d929e592a91 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 15:48:56 -0500 Subject: [PATCH 2/9] update --- src/arithematics.jl | 112 ++++++++++++++++++++++++++++--------------- test/arithematics.jl | 22 +++++++++ 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/arithematics.jl b/src/arithematics.jl index 4d248567..35b28244 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -2,7 +2,9 @@ using Polynomials: Polynomial using TropicalNumbers: Tropical, CountingTropical using Mods, Primes using Base.Cartesian -import AbstractTrees +import AbstractTrees: children, printnode + +@enum TreeTag LEAF SUM PROD # pirate Base.abs(x::Mod) = x @@ -279,64 +281,97 @@ Base.one(::ConfigSampler{N,S,C}) where {N,S,C} = one(ConfigSampler{N,S,C}) # tree config enumerator export TreeConfigEnumerator struct TreeConfigEnumerator{N,S,C} + tag::TreeTag siblings::Vector{TreeConfigEnumerator{N,S,C}} - isleaf::Bool data::StaticElementVector{N,S,C} - TreeConfigEnumerator(v::Vector{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = new{N,S,C}(v, false) - TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} = new{N,S,C}(StaticElementVector{N,S,C}[], true, data) + TreeConfigEnumerator(tag::TreeTag, v::Vector{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = new{N,S,C}(tag, v) + TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} = new{N,S,C}(LEAF, StaticElementVector{N,S,C}[], data) end -isleaf(t::TreeConfigEnumerator) = t.isleaf -AbstractTrees.children(t::TreeConfigEnumerator) = t.siblings -AbstractTrees.printnode(io::IO, t::TreeConfigEnumerator) = isleaf(t) ? print(io, t.data) : print(io, "○") +children(t::TreeConfigEnumerator) = t.siblings +function printnode(io::IO, t::TreeConfigEnumerator) + if t.tag == LEAF + print(io, t.data) + elseif t.tag == SUM + print(io, "+") + else # PROD + print(io, "*") + end +end -Base.length(x::TreeConfigEnumerator{N}) where N = isleaf(x) ? 1 : prod(length, x.siblings) -Base.eltype(::Type{<:AbstractTrees.TreeIterator{TreeConfigEnumerator{N,S,C}}}) where {N,S,C} = StaticElementVector{N,S,C} -Base.IteratorEltype(::Type{<:AbstractTrees.TreeIterator{TreeConfigEnumerator{N, S, C}}}) where {N,S,C} = Base.HasEltype()a -function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} - if isleaf(x) && isleaf(y) - return x.data == y.data - elseif !isleaf(x) && !isleaf(y) && length(x.siblings) == length(y.siblings) - return all(i->x.siblings[i] == y.siblings[i], lengh(x.siblings)) +function Base.length(x::TreeConfigEnumerator) + if x.tag == LEAF + 1 + elseif x.tag == SUM + isempty(children(x)) && return 0 + sum(length, children(x)) else - return false + isempty(children(x)) && return 1 + prod(length, children(x)) + end +end + +function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} + return Set(collect(x)) == Set(collect(y)) + #(x.tag != y.tag || length(children(x)) == length(children(y))) && return false + #if x.tag == LEAF + #return x.data == y.data + #else + #return Set(children(x)) == Set(children(y)) + #end +end + +function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} + if x.tag == LEAF + return StaticElementVector{N,S,C}[x.data] + elseif x.tag == SUM + sets = collect.(x.siblings) + return vcat(sets...) + else # PROD + sets = collect.(x.siblings) + return vec([reduce((x,y)->x|y, si) for si in Iterators.product(sets...)]) end end function Base.:+(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} - iszero(x) && return y - iszero(y) && return x - return TreeConfigEnumerator(vcat(x.siblings, y.siblings)) + return TreeConfigEnumerator(SUM, [x, y]) end function Base.:*(x::TreeConfigEnumerator{L,S,C}, y::TreeConfigEnumerator{L,S,C}) where {L,S,C} - iszero(x) && return x - iszero(y) && return y - return TreeConfigEnumerator([x, y]) + return TreeConfigEnumerator(PROD, [x, y]) end -Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(TreeConfigEnumerator{N,S,C}[]) -Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator([TreeConfigEnumerator(zero(StaticElementVector{N,S,C}))]) +Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(SUM, TreeConfigEnumerator{N,S,C}[]) +Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(SUM, [TreeConfigEnumerator(zero(StaticElementVector{N,S,C}))]) Base.zero(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = zero(TreeConfigEnumerator{N,S,C}) Base.one(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = one(TreeConfigEnumerator{N,S,C}) # todo, check siblings too? -Base.iszero(t::TreeConfigEnumerator) = isempty(t.siblings) -Base.iterate(t::TreeConfigEnumerator) = iterate(AbstractTrees.PreOrderDFS(t)) +function Base.iszero(t::TreeConfigEnumerator) + if t.TAG == SUM + all(isempty, t.siblings) + elseif t.TAG == LEAF + false + else + any(isempty, t.siblings) + end +end # A patch to make `Polynomial{ConfigEnumerator}` work -function Base.:*(a::Int, y::ConfigEnumerator) - a == 0 && return zero(y) - a == 1 && return y - error("multiplication between int and config enumerator is not defined.") -end -function Base.:*(a::Int, y::ConfigSampler) - a == 0 && return zero(y) - a == 1 && return y - error("multiplication between int and config sampler is not defined.") +for T in [:ConfigEnumerator, :ConfigSampler, :TreeConfigEnumerator] + @eval function Base.:*(a::Int, y::$T) + a == 0 && return zero(y) + a == 1 && return y + error("multiplication between int and `$(typeof(y))` is not defined.") + end + @eval function Base.:*(a::Int, y::$T) + a == 0 && return zero(y) + a == 1 && return y + error("multiplication between int and `$(typeof(y))` is not defined.") + end end # convert from counting type to bitstring type -for (F,TP) in [(:set_type, :ConfigEnumerator), (:sampler_type, :ConfigSampler)] +for (F,TP) in [(:set_type, :ConfigEnumerator), (:sampler_type, :ConfigSampler), (:treesampler_type, :TreeConfigEnumerator)] @eval begin function $F(::Type{T}, n::Int, nflavor::Int) where {OT, K, T<:TruncatedPoly{K,C,OT} where C} TruncatedPoly{K, $F(n,nflavor),OT} @@ -360,12 +395,13 @@ end # utilities for creating onehot vectors onehotv(::Type{ConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigEnumerator([onehotv(StaticElementVector{N,S,C}, i, v)]) -onehotv(::Type{TreeConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = TreeConfigEnumerator([TreeConfigEnumerator(onehotv(StaticElementVector{N,S,C}, i, v))]) +onehotv(::Type{TreeConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = TreeConfigEnumerator(SUM, [TreeConfigEnumerator(onehotv(StaticElementVector{N,S,C}, i, v))]) onehotv(::Type{ConfigSampler{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigSampler(onehotv(StaticElementVector{N,S,C}, i, v)) +# just to make matrix transpose work Base.transpose(c::ConfigEnumerator) = c Base.copy(c::ConfigEnumerator) = ConfigEnumerator(copy(c.data)) Base.transpose(c::TreeConfigEnumerator) = c -Base.copy(c::TreeConfigEnumerator) = isleaf(c) ? TreeConfigEnumerator(c.data) : TreeConfigEnumerator(c.siblings) +Base.copy(c::TreeConfigEnumerator) = c.tag == LEAF ? TreeConfigEnumerator(c.data) : TreeConfigEnumerator(c.tag, c.siblings) # Handle boolean, this is a patch for CUDA matmul for TYPE in [:ConfigEnumerator, :ConfigSampler, :TruncatedPoly, :TreeConfigEnumerator] diff --git a/test/arithematics.jl b/test/arithematics.jl index 60027722..a45a2d6f 100644 --- a/test/arithematics.jl +++ b/test/arithematics.jl @@ -38,6 +38,18 @@ end (CountingTropical(5.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(3.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(-3.0, ConfigSampler(StaticBitVector(rand(Bool, 10))))), (CountingTropical(5.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:3])), CountingTropical(3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:4])), CountingTropical(-3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:5]))), (ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:3]), ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:4]), ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:5])), + (TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:3]), + TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:4]), + TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:5]) + ), + (TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:3]), + TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:4]), + TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:5]) + ), + (TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))), + TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))), + TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))), + ), ] @test is_commutative_semiring(a, b, c) @test false * a == zero(a) @@ -53,6 +65,16 @@ end @test copy(a) == a @test length.(a) == [10, 10, 10] @test map(x->length(x), a) == [10, 10, 10] + + # the following tests are for Polynomial + ConfigEnumerator + a = TreeConfigEnumerator(StaticBitVector(trues(10))) + @test 1 * a == a + @test 0 * a == zero(a) + @test copy(a) == a + @test length(a) == 1 + @test length(a + a) == 2 + @test length(a * a) == 1 + a = ConfigSampler(StaticBitVector(rand(Bool, 10))) @test 1 * a == a @test 0 * a == zero(a) From 7bf49927b6284131f2ddd757e90c5091b0c7a603 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 16:36:07 -0500 Subject: [PATCH 3/9] done --- src/GraphTensorNetworks.jl | 2 +- src/arithematics.jl | 7 +----- src/configurations.jl | 26 +++++++++++----------- src/interfaces.jl | 44 +++++++++++++++++++++----------------- src/networks/PaintShop.jl | 4 ++-- test/interfaces.jl | 33 ++++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/GraphTensorNetworks.jl b/src/GraphTensorNetworks.jl index 2a3c3e0d..72bb39c2 100644 --- a/src/GraphTensorNetworks.jl +++ b/src/GraphTensorNetworks.jl @@ -14,7 +14,7 @@ export GreedyMethod, TreeSA, SABipartite, KaHyParBipartite, MergeVectors, MergeG # Algebras export StaticBitVector, StaticElementVector, @bv_str export is_commutative_semiring -export Max2Poly, TruncatedPoly, Polynomial, Tropical, CountingTropical, StaticElementVector, Mod, ConfigEnumerator, onehotv, ConfigSampler +export Max2Poly, TruncatedPoly, Polynomial, Tropical, CountingTropical, StaticElementVector, Mod, ConfigEnumerator, onehotv, ConfigSampler, TreeConfigEnumerator export CountingTropicalF64, CountingTropicalF32, TropicalF64, TropicalF32 # Lower level APIs diff --git a/src/arithematics.jl b/src/arithematics.jl index 35b28244..53e74621 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -363,15 +363,10 @@ for T in [:ConfigEnumerator, :ConfigSampler, :TreeConfigEnumerator] a == 1 && return y error("multiplication between int and `$(typeof(y))` is not defined.") end - @eval function Base.:*(a::Int, y::$T) - a == 0 && return zero(y) - a == 1 && return y - error("multiplication between int and `$(typeof(y))` is not defined.") - end end # convert from counting type to bitstring type -for (F,TP) in [(:set_type, :ConfigEnumerator), (:sampler_type, :ConfigSampler), (:treesampler_type, :TreeConfigEnumerator)] +for (F,TP) in [(:set_type, :ConfigEnumerator), (:sampler_type, :ConfigSampler), (:treeset_type, :TreeConfigEnumerator)] @eval begin function $F(::Type{T}, n::Int, nflavor::Int) where {OT, K, T<:TruncatedPoly{K,C,OT} where C} TruncatedPoly{K, $F(n,nflavor),OT} diff --git a/src/configurations.jl b/src/configurations.jl index 2c8e8536..6bf0da17 100644 --- a/src/configurations.jl +++ b/src/configurations.jl @@ -1,13 +1,14 @@ """ - best_solutions(problem; all=false, usecuda=false, invert=false) + best_solutions(problem; all=false, usecuda=false, invert=false, tree_storage::Bool=false) Find optimal solutions with bounding. * When `all` is true, the program will use set for enumerate all possible solutions, otherwise, it will return one solution for each size. * `usecuda` can not be true if you want to use set to enumerate all possible solutions. * If `invert` is true, find the minimum. +* If `tree_storage` is true, use [`TreeConfigEnumerator`](@ref) as the storage of solutions. """ -function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false) +function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false, tree_storage::Bool) if all && usecuda throw(ArgumentError("ConfigEnumerator can not be computed on GPU!")) end @@ -19,7 +20,7 @@ function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false end if all # we use `Float64` types because we want to support weighted graphs. - xs = generate_tensors(fx_solutions(gp, CountingTropical{Float64,Float64}, all, invert), gp) + xs = generate_tensors(fx_solutions(gp, CountingTropical{Float64,Float64}, all, invert, tree_storage), gp) ret = bounding_contract(AllConfigs{1}(), gp.code, xst, ymask, xs) return invert ? post_invert_exponent.(ret) : ret else @@ -31,7 +32,7 @@ function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false end """ - solutions(problem, basetype; all, usecuda=false, invert=false) + solutions(problem, basetype; all, usecuda=false, invert=false, tree_storage::Bool=false) General routine to find solutions without bounding, @@ -41,26 +42,27 @@ General routine to find solutions without bounding, * `Max2Poly{Float64,Float64}` for optimal and suboptimal solutions. * When `all` is true, the program will use set for enumerate all possible solutions, otherwise, it will return one solution for each size. * `usecuda` can not be true if you want to use set to enumerate all possible solutions. +* If `tree_storage` is true, use [`TreeConfigEnumerator`](@ref) as the storage of solutions. """ -function solutions(gp::GraphProblem, ::Type{BT}; all::Bool, usecuda::Bool=false, invert::Bool=false) where BT +function solutions(gp::GraphProblem, ::Type{BT}; all::Bool, usecuda::Bool=false, invert::Bool=false, tree_storage::Bool=false) where BT if all && usecuda throw(ArgumentError("ConfigEnumerator can not be computed on GPU!")) end - ret = contractf(fx_solutions(gp, BT, all, invert), gp; usecuda=usecuda) + ret = contractf(fx_solutions(gp, BT, all, invert, tree_storage), gp; usecuda=usecuda) return invert ? post_invert_exponent.(ret) : ret end """ - best2_solutions(problem; all=true, usecuda=false, invert=false) + best2_solutions(problem; all=true, usecuda=false, invert=false, tree_storage::Bool=false) Finding optimal and suboptimal solutions. """ best2_solutions(gp::GraphProblem; all=true, usecuda=false, invert::Bool=false) = solutions(gp, Max2Poly{Float64,Float64}; all, usecuda, invert) -function bestk_solutions(gp::GraphProblem, k::Int; invert::Bool=false) +function bestk_solutions(gp::GraphProblem, k::Int; invert::Bool=false, tree_storage::Bool=false) xst = generate_tensors(l->TropicalF64.(get_weights(gp, l)), gp) ymask = trues(fill(2, length(getiyv(gp.code)))...) - xs = generate_tensors(fx_solutions(gp, TruncatedPoly{k,Float64,Float64}, true, invert), gp) + xs = generate_tensors(fx_solutions(gp, TruncatedPoly{k,Float64,Float64}, true, invert, tree_storage), gp) ret = bounding_contract(AllConfigs{k}(), gp.code, xst, ymask, xs) return invert ? post_invert_exponent.(ret) : ret end @@ -74,9 +76,9 @@ e.g. when the problem is [`MaximalIS`](@ref), it computes all maximal independen all_solutions(gp::GraphProblem) = solutions(gp, Polynomial{Float64,:x}, all=true, usecuda=false) # return a mapping from label to onehot bitstrings (degree of freedoms). -function fx_solutions(gp::GraphProblem, ::Type{BT}, all::Bool, invert::Bool) where {BT} +function fx_solutions(gp::GraphProblem, ::Type{BT}, all::Bool, invert::Bool, tree_storage::Bool) where {BT} syms = symbols(gp) - T = (all ? set_type : sampler_type)(BT, length(syms), nflavor(gp)) + T = (all ? (tree_storage ? treeset_type : set_type) : sampler_type)(BT, length(syms), nflavor(gp)) vertex_index = Dict([s=>i for (i, s) in enumerate(syms)]) return function (l) ret = _onehotv.(Ref(T), vertex_index[l], flavors(gp), get_weights(gp, l)) @@ -92,6 +94,6 @@ end function _onehotv(::Type{CountingTropical{TV,BS}}, x, v, w) where {TV,BS} CountingTropical{TV,BS}(TV(w), onehotv(BS, x, v)) end -function _onehotv(::Type{BS}, x, v, w) where {BS<:ConfigEnumerator} +function _onehotv(::Type{BS}, x, v, w) where {BS<:Union{ConfigEnumerator, TreeConfigEnumerator}} onehotv(BS, x, v) end \ No newline at end of file diff --git a/src/interfaces.jl b/src/interfaces.jl index 1916f642..ad8acc8a 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -124,19 +124,21 @@ struct SingleConfigMin{BOUNDED} <:AbstractProperty end SingleConfigMin(; bounded::Bool=false) = SingleConfigMin{bounded}() """ - ConfigsAll <:AbstractProperty - ConfigsAll() + ConfigsAll{TREESTORAGE} <:AbstractProperty + ConfigsAll(; tree_storage=false) Find all valid configurations, e.g. for [`IndependentSet`](@ref) problem, it is finding all independent sets. * The corresponding data type is [`ConfigEnumerator`](@ref). * Weights do not take effect. """ -struct ConfigsAll <:AbstractProperty end +struct ConfigsAll{TREESTORAGE} <:AbstractProperty end +ConfigsAll(; tree_storage::Bool=false) = ConfigsAll{tree_storage}() +tree_storage(::ConfigsAll{TREESTORAGE}) where {TREESTORAGE} = TREESTORAGE """ - ConfigsMax{K, BOUNDED} <:AbstractProperty - ConfigsMax(K=1; bounded=true) + ConfigsMax{K, BOUNDED, TREESTORAGE} <:AbstractProperty + ConfigsMax(K=1; bounded=true, tree_storage=true) Find configurations with largest-K sizes, e.g. for [`IndependentSet`](@ref) problem, it is finding all independent sets of sizes ``\\alpha(G), \\alpha(G)-1, \\ldots, \\alpha(G)-K+1``. @@ -144,22 +146,24 @@ it is finding all independent sets of sizes ``\\alpha(G), \\alpha(G)-1, \\ldots, * The corresponding data type is [`CountingTropical`](@ref)`{Float64,<:ConfigEnumerator}` for `K == 1` and [`TruncatedPoly`](@ref)`{K,<:ConfigEnumerator}` for `K > 1`. * Weighted graph problems is only supported for `K == 1`. """ -struct ConfigsMax{K, BOUNDED} <:AbstractProperty end -ConfigsMax(K::Int=1; bounded::Bool=true) = ConfigsMax{K,bounded}() +struct ConfigsMax{K, BOUNDED, TREESTORAGE} <:AbstractProperty end +ConfigsMax(K::Int=1; bounded::Bool=true, tree_storage::Bool=false) = ConfigsMax{K,bounded,tree_storage}() max_k(::ConfigsMax{K}) where K = K +tree_storage(::ConfigsMax{K,BOUNDED,TREESTORAGE}) where {K,BOUNDED,TREESTORAGE} = TREESTORAGE """ - ConfigsMin{K, BOUNDED} <:AbstractProperty - ConfigsMin(K=1; bounded=true) + ConfigsMin{K, BOUNDED, TREESTORAGE} <:AbstractProperty + ConfigsMin(K=1; bounded=true, tree_storage::Bool=false) Find configurations with smallest-K sizes. * The corresponding data type is inverted [`CountingTropical`](@ref)`{Float64,<:ConfigEnumerator}` for `K == 1` and inverted [`TruncatedPoly`](@ref)`{K,<:ConfigEnumerator}` for `K > 1`. * Weighted graph problems is only supported for `K == 1`. """ -struct ConfigsMin{K, BOUNDED} <:AbstractProperty end -ConfigsMin(K::Int=1; bounded::Bool=true) = ConfigsMin{K,bounded}() +struct ConfigsMin{K, BOUNDED, TREESTORAGE} <:AbstractProperty end +ConfigsMin(K::Int=1; bounded::Bool=true, tree_storage::Bool=false) = ConfigsMin{K,bounded, tree_storage}() min_k(::ConfigsMin{K}) where K = K +tree_storage(::ConfigsMin{K,BOUNDED,TREESTORAGE}) where {K,BOUNDED,TREESTORAGE} = TREESTORAGE """ solve(problem, property; usecuda=false, T=Float64) @@ -212,31 +216,31 @@ function solve(gp::GraphProblem, property::AbstractProperty; T=Float64, usecuda= elseif property isa GraphPolynomial return graph_polynomial(gp, Val(graph_polynomial_method(property)); usecuda=usecuda, property.kwargs...) elseif property isa SingleConfigMax{false} - return solutions(gp, CountingTropical{T,T}; all=false, usecuda=usecuda) + return solutions(gp, CountingTropical{T,T}; all=false, usecuda=usecuda, ) elseif property isa SingleConfigMin{false} return solutions(gp, CountingTropical{T,T}; all=false, usecuda=usecuda, invert=true) elseif property isa ConfigsMax{1,false} - return solutions(gp, CountingTropical{T,T}; all=true, usecuda=usecuda) + return solutions(gp, CountingTropical{T,T}; all=true, usecuda=usecuda, tree_storage=tree_storage(property)) elseif property isa ConfigsMin{1,false} - return solutions(gp, CountingTropical{T,T}; all=true, usecuda=usecuda, invert=true) + return solutions(gp, CountingTropical{T,T}; all=true, usecuda=usecuda, invert=true, tree_storage=tree_storage(property)) elseif property isa (ConfigsMax{K, false} where K) - return solutions(gp, TruncatedPoly{max_k(property),T,T}; all=true, usecuda=usecuda) + return solutions(gp, TruncatedPoly{max_k(property),T,T}; all=true, usecuda=usecuda, tree_storage=tree_storage(property)) elseif property isa (ConfigsMin{K, false} where K) return solutions(gp, TruncatedPoly{min_k(property),T,T}; all=true, usecuda=usecuda, invert=true) elseif property isa ConfigsAll - return solutions(gp, Real; all=true, usecuda=usecuda) + return solutions(gp, Real; all=true, usecuda=usecuda, tree_storage=tree_storage(property)) elseif property isa SingleConfigMax{true} return best_solutions(gp; all=false, usecuda=usecuda) elseif property isa SingleConfigMin{true} return best_solutions(gp; all=false, usecuda=usecuda, invert=true) elseif property isa ConfigsMax{1,true} - return best_solutions(gp; all=true, usecuda=usecuda) + return best_solutions(gp; all=true, usecuda=usecuda, tree_storage=tree_storage(property)) elseif property isa ConfigsMin{1,true} - return best_solutions(gp; all=true, usecuda=usecuda, invert=true) + return best_solutions(gp; all=true, usecuda=usecuda, invert=true, tree_storage=tree_storage(property)) elseif property isa (ConfigsMax{K,true} where K) - return bestk_solutions(gp, max_k(property)) + return bestk_solutions(gp, max_k(property), tree_storage=tree_storage(property)) elseif property isa (ConfigsMin{K,true} where K) - return bestk_solutions(gp, min_k(property), invert=true) + return bestk_solutions(gp, min_k(property), invert=true, tree_storage=tree_storage(property)) else error("unknown property $property.") end diff --git a/src/networks/PaintShop.jl b/src/networks/PaintShop.jl index f150b8ad..6e7f850f 100644 --- a/src/networks/PaintShop.jl +++ b/src/networks/PaintShop.jl @@ -103,9 +103,9 @@ function paint_shop_coloring_from_config(config; initial::Bool=false) return res end -function fx_solutions(gp::PaintShop, ::Type{BT}, all::Bool, invert::Bool) where {BT} +function fx_solutions(gp::PaintShop, ::Type{BT}, all::Bool, invert::Bool, tree_storage::Bool) where {BT} syms = symbols(gp) - T = (all ? set_type : sampler_type)(BT, length(syms), nflavor(gp)) + T = (all ? (tree_storage ? treeset_type : set_type) : sampler_type)(BT, length(syms), nflavor(gp)) counter = Ref(0) return function (l) ret = _onehotv.(Ref(T), (counter[]+=1; counter[]), flavors(gp), get_weights(gp, l)) diff --git a/test/interfaces.jl b/test/interfaces.jl index d8d81cef..f7c1fa54 100644 --- a/test/interfaces.jl +++ b/test/interfaces.jl @@ -141,4 +141,37 @@ end res4b = solve(gp, ConfigsMax(1; bounded=true))[] @test res4 == res4b @test res4.c.data[] == res3.c.data +end + +@testset "tree storage" begin + g = smallgraph(:petersen) + gp = IndependentSet(g) + res1 = solve(gp, ConfigsAll(; tree_storage=true))[] + res2 = solve(gp, ConfigsAll(; tree_storage=false))[] + @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test Set(res2 |> collect) == Set(res1 |> collect) + + res1 = solve(gp, ConfigsMax(; tree_storage=true))[].c + res2 = solve(gp, ConfigsMax(; tree_storage=false))[].c + @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test Set(res2 |> collect) == Set(res1 |> collect) + + res1s = solve(gp, ConfigsMax(2; tree_storage=true))[].coeffs + res2s = solve(gp, ConfigsMax(2; tree_storage=false))[].coeffs + for (res1, res2) in zip(res1s, res2s) + @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test Set(res2 |> collect) == Set(res1 |> collect) + end + + res1 = solve(gp, ConfigsMin(; tree_storage=true))[].c + res2 = solve(gp, ConfigsMin(; tree_storage=false))[].c + @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test Set(res2 |> collect) == Set(res1 |> collect) + + res1s = solve(gp, ConfigsMin(2; tree_storage=true))[].coeffs + res2s = solve(gp, ConfigsMin(2; tree_storage=false))[].coeffs + for (res1, res2) in zip(res1s, res2s) + @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test Set(res2 |> collect) == Set(res1 |> collect) + end end \ No newline at end of file From 767fa3213c93289a5a274e84a99cc837cb4bb94d Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 17:53:27 -0500 Subject: [PATCH 4/9] work but slow length --- src/arithematics.jl | 30 +++++++++++++++++++----------- test/interfaces.jl | 5 +++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/arithematics.jl b/src/arithematics.jl index 53e74621..6f9a87b6 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -2,7 +2,7 @@ using Polynomials: Polynomial using TropicalNumbers: Tropical, CountingTropical using Mods, Primes using Base.Cartesian -import AbstractTrees: children, printnode +import AbstractTrees: children, printnode, print_tree @enum TreeTag LEAF SUM PROD @@ -290,24 +290,30 @@ end children(t::TreeConfigEnumerator) = t.siblings function printnode(io::IO, t::TreeConfigEnumerator) - if t.tag == LEAF + if t.tag === LEAF print(io, t.data) - elseif t.tag == SUM + elseif t.tag === SUM print(io, "+") else # PROD print(io, "*") end end -function Base.length(x::TreeConfigEnumerator) - if x.tag == LEAF - 1 - elseif x.tag == SUM - isempty(children(x)) && return 0 - sum(length, children(x)) +@inline function Base.length(x::TreeConfigEnumerator) + if x.tag === LEAF + return 1 + elseif x.tag === SUM + res = 0 + for sib in x.siblings + res += length(sib) + end + return res else - isempty(children(x)) && return 1 - prod(length, children(x)) + res = 1 + for sib in x.siblings + res *= length(sib) + end + return res end end @@ -321,6 +327,8 @@ function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S, #end end +Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) + function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} if x.tag == LEAF return StaticElementVector{N,S,C}[x.data] diff --git a/test/interfaces.jl b/test/interfaces.jl index f7c1fa54..6c5f26cf 100644 --- a/test/interfaces.jl +++ b/test/interfaces.jl @@ -149,29 +149,34 @@ end res1 = solve(gp, ConfigsAll(; tree_storage=true))[] res2 = solve(gp, ConfigsAll(; tree_storage=false))[] @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test length(res1) == length(res2) @test Set(res2 |> collect) == Set(res1 |> collect) res1 = solve(gp, ConfigsMax(; tree_storage=true))[].c res2 = solve(gp, ConfigsMax(; tree_storage=false))[].c @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test length(res1) == length(res2) @test Set(res2 |> collect) == Set(res1 |> collect) res1s = solve(gp, ConfigsMax(2; tree_storage=true))[].coeffs res2s = solve(gp, ConfigsMax(2; tree_storage=false))[].coeffs for (res1, res2) in zip(res1s, res2s) @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test length(res1) == length(res2) @test Set(res2 |> collect) == Set(res1 |> collect) end res1 = solve(gp, ConfigsMin(; tree_storage=true))[].c res2 = solve(gp, ConfigsMin(; tree_storage=false))[].c @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test length(res1) == length(res2) @test Set(res2 |> collect) == Set(res1 |> collect) res1s = solve(gp, ConfigsMin(2; tree_storage=true))[].coeffs res2s = solve(gp, ConfigsMin(2; tree_storage=false))[].coeffs for (res1, res2) in zip(res1s, res2s) @test res1 isa TreeConfigEnumerator && res2 isa ConfigEnumerator + @test length(res1) == length(res2) @test Set(res2 |> collect) == Set(res1 |> collect) end end \ No newline at end of file From 31cf13186a547bd59ce6ca9a07b8f5bef2c3f9e3 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 18:45:46 -0500 Subject: [PATCH 5/9] non-binary --- src/arithematics.jl | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/arithematics.jl b/src/arithematics.jl index 6f9a87b6..10e172a7 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -299,24 +299,32 @@ function printnode(io::IO, t::TreeConfigEnumerator) end end -@inline function Base.length(x::TreeConfigEnumerator) - if x.tag === LEAF - return 1 - elseif x.tag === SUM - res = 0 - for sib in x.siblings - res += length(sib) +function Base.length(x::TreeConfigEnumerator) + if x.tag === SUM + isempty(x.siblings) && return 0 + res = length(first(x.siblings)) + for i = 2:length(x.siblings) + res += length(x.siblings[i]) end return res - else - res = 1 - for sib in x.siblings - res *= length(sib) + elseif x.tag === PROD + isempty(x.siblings) && return 1 + res = length(first(x.siblings)) + for i = 2:length(x.siblings) + res *= length(x.siblings[i]) end return res + else + return 1 end end +function num_nodes(x::TreeConfigEnumerator) + x.tag == LEAF && return 1 + isempty(x.siblings) && return 0 + return sum(num_nodes, x.siblings) +end + function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} return Set(collect(x)) == Set(collect(y)) #(x.tag != y.tag || length(children(x)) == length(children(y))) && return false @@ -327,7 +335,7 @@ function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S, #end end -Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) +#Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} if x.tag == LEAF @@ -342,11 +350,27 @@ function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} end function Base.:+(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} - return TreeConfigEnumerator(SUM, [x, y]) + if x.tag === y.tag === SUM + return TreeConfigEnumerator(SUM, [x.siblings..., y.siblings...]) + elseif x.tag === SUM + return TreeConfigEnumerator(SUM, [x.siblings..., y]) + elseif y.tag === SUM + return TreeConfigEnumerator(SUM, [x, y.siblings...]) + else + return TreeConfigEnumerator(SUM, [x, y]) + end end function Base.:*(x::TreeConfigEnumerator{L,S,C}, y::TreeConfigEnumerator{L,S,C}) where {L,S,C} - return TreeConfigEnumerator(PROD, [x, y]) + if x.tag === y.tag === PROD + return TreeConfigEnumerator(PROD, [x.siblings..., y.siblings...]) + elseif x.tag === PROD + return TreeConfigEnumerator(PROD, [x.siblings..., y]) + elseif y.tag === PROD + return TreeConfigEnumerator(PROD, [x, y.siblings...]) + else + return TreeConfigEnumerator(PROD, [x, y]) + end end Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(SUM, TreeConfigEnumerator{N,S,C}[]) From 638dcf97335d6e70a5271371901a9eeaa5366d5e Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 18 Feb 2022 21:18:28 -0500 Subject: [PATCH 6/9] update --- src/arithematics.jl | 97 ++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/src/arithematics.jl b/src/arithematics.jl index 10e172a7..8c51ecfb 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -4,7 +4,7 @@ using Mods, Primes using Base.Cartesian import AbstractTrees: children, printnode, print_tree -@enum TreeTag LEAF SUM PROD +@enum TreeTag LEAF SUM PROD ZERO # pirate Base.abs(x::Mod) = x @@ -280,18 +280,25 @@ Base.one(::ConfigSampler{N,S,C}) where {N,S,C} = one(ConfigSampler{N,S,C}) # tree config enumerator export TreeConfigEnumerator -struct TreeConfigEnumerator{N,S,C} +mutable struct TreeConfigEnumerator{N,S,C} tag::TreeTag - siblings::Vector{TreeConfigEnumerator{N,S,C}} + left::TreeConfigEnumerator{N,S,C} + right::TreeConfigEnumerator{N,S,C} data::StaticElementVector{N,S,C} - TreeConfigEnumerator(tag::TreeTag, v::Vector{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = new{N,S,C}(tag, v) - TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} = new{N,S,C}(LEAF, StaticElementVector{N,S,C}[], data) + TreeConfigEnumerator(tag::TreeTag, left::TreeConfigEnumerator{N,S,C}, right::TreeConfigEnumerator{N,S,C}) where {N,S,C} = new{N,S,C}(tag, left, right) + function TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} + res = new{N,S,C}(LEAF) + res.data = data + return res + end end -children(t::TreeConfigEnumerator) = t.siblings +children(t::TreeConfigEnumerator) = (t.left, t.right) function printnode(io::IO, t::TreeConfigEnumerator) if t.tag === LEAF print(io, t.data) + elseif t.tag === ZERO + print(io, "") elseif t.tag === SUM print(io, "+") else # PROD @@ -301,90 +308,62 @@ end function Base.length(x::TreeConfigEnumerator) if x.tag === SUM - isempty(x.siblings) && return 0 - res = length(first(x.siblings)) - for i = 2:length(x.siblings) - res += length(x.siblings[i]) - end - return res + return length(x.left) + length(x.right) elseif x.tag === PROD - isempty(x.siblings) && return 1 - res = length(first(x.siblings)) - for i = 2:length(x.siblings) - res *= length(x.siblings[i]) - end - return res + return length(x.left) * length(x.right) + elseif x.tag === ZERO + return 0 else return 1 end end function num_nodes(x::TreeConfigEnumerator) + x.tag == ZERO && return 0 x.tag == LEAF && return 1 - isempty(x.siblings) && return 0 - return sum(num_nodes, x.siblings) + return num_nodes(x.left) + num_nodes(x.right) end function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} return Set(collect(x)) == Set(collect(y)) - #(x.tag != y.tag || length(children(x)) == length(children(y))) && return false - #if x.tag == LEAF - #return x.data == y.data - #else - #return Set(children(x)) == Set(children(y)) - #end end #Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} - if x.tag == LEAF + if x.tag == ZERO + return StaticElementVector{N,S,C}[] + elseif x.tag == LEAF return StaticElementVector{N,S,C}[x.data] elseif x.tag == SUM - sets = collect.(x.siblings) - return vcat(sets...) + return vcat(collect(x.left), collect(x.right)) else # PROD - sets = collect.(x.siblings) - return vec([reduce((x,y)->x|y, si) for si in Iterators.product(sets...)]) + return vec([reduce((x,y)->x|y, si) for si in Iterators.product(collect(x.left), collect(x.right))]) end end function Base.:+(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} - if x.tag === y.tag === SUM - return TreeConfigEnumerator(SUM, [x.siblings..., y.siblings...]) - elseif x.tag === SUM - return TreeConfigEnumerator(SUM, [x.siblings..., y]) - elseif y.tag === SUM - return TreeConfigEnumerator(SUM, [x, y.siblings...]) - else - return TreeConfigEnumerator(SUM, [x, y]) - end + TreeConfigEnumerator(SUM, x, y) end function Base.:*(x::TreeConfigEnumerator{L,S,C}, y::TreeConfigEnumerator{L,S,C}) where {L,S,C} - if x.tag === y.tag === PROD - return TreeConfigEnumerator(PROD, [x.siblings..., y.siblings...]) - elseif x.tag === PROD - return TreeConfigEnumerator(PROD, [x.siblings..., y]) - elseif y.tag === PROD - return TreeConfigEnumerator(PROD, [x, y.siblings...]) - else - return TreeConfigEnumerator(PROD, [x, y]) - end + TreeConfigEnumerator(PROD, x, y) end -Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(SUM, TreeConfigEnumerator{N,S,C}[]) -Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(SUM, [TreeConfigEnumerator(zero(StaticElementVector{N,S,C}))]) +Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(ZERO) +Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(zero(StaticElementVector{N,S,C})) Base.zero(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = zero(TreeConfigEnumerator{N,S,C}) Base.one(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = one(TreeConfigEnumerator{N,S,C}) # todo, check siblings too? function Base.iszero(t::TreeConfigEnumerator) if t.TAG == SUM - all(isempty, t.siblings) + iszero(t.left) && iszero(t.right) + elseif t.TAG == ZERO + true elseif t.TAG == LEAF false else - any(isempty, t.siblings) + iszero(t.left) || iszero(t.right) end end @@ -422,13 +401,21 @@ end # utilities for creating onehot vectors onehotv(::Type{ConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigEnumerator([onehotv(StaticElementVector{N,S,C}, i, v)]) -onehotv(::Type{TreeConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = TreeConfigEnumerator(SUM, [TreeConfigEnumerator(onehotv(StaticElementVector{N,S,C}, i, v))]) +onehotv(::Type{TreeConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = TreeConfigEnumerator(onehotv(StaticElementVector{N,S,C}, i, v)) onehotv(::Type{ConfigSampler{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigSampler(onehotv(StaticElementVector{N,S,C}, i, v)) # just to make matrix transpose work Base.transpose(c::ConfigEnumerator) = c Base.copy(c::ConfigEnumerator) = ConfigEnumerator(copy(c.data)) Base.transpose(c::TreeConfigEnumerator) = c -Base.copy(c::TreeConfigEnumerator) = c.tag == LEAF ? TreeConfigEnumerator(c.data) : TreeConfigEnumerator(c.tag, c.siblings) +function Base.copy(c::TreeConfigEnumerator) + if c.tag == LEAF + TreeConfigEnumerator(c.data) + elseif c.tag == ZERO + TreeConfigEnumerator(c.tag) + else + TreeConfigEnumerator(c.tag, c.left, c.right) + end +end # Handle boolean, this is a patch for CUDA matmul for TYPE in [:ConfigEnumerator, :ConfigSampler, :TruncatedPoly, :TreeConfigEnumerator] From 131d969449dd805d070db66c40370541870848e6 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 19 Feb 2022 00:09:24 -0500 Subject: [PATCH 7/9] tree storage fully tested --- Project.toml | 1 + docs/src/ref.md | 1 + examples/IndependentSet.jl | 11 +++++ src/arithematics.jl | 97 +++++++++++++++++++++++++++++++++----- src/bounding.jl | 1 - src/configurations.jl | 4 +- src/interfaces.jl | 36 +++++++++++--- test/arithematics.jl | 13 ++--- 8 files changed, 137 insertions(+), 27 deletions(-) diff --git a/Project.toml b/Project.toml index 76b361cd..c66e2a7b 100644 --- a/Project.toml +++ b/Project.toml @@ -39,6 +39,7 @@ SIMDTypes = "0.1" StatsBase = "0.33" TropicalNumbers = "0.4, 0.5" Viznet = "0.3" +AbstractTrees = 0.3 julia = "1" [extras] diff --git a/docs/src/ref.md b/docs/src/ref.md index 749a08d3..33c9c6cb 100644 --- a/docs/src/ref.md +++ b/docs/src/ref.md @@ -68,6 +68,7 @@ Polynomials.Polynomial TruncatedPoly Max2Poly ConfigEnumerator +TreeConfigEnumerator ConfigSampler ``` diff --git a/examples/IndependentSet.jl b/examples/IndependentSet.jl index 5b76f8a2..55a30093 100644 --- a/examples/IndependentSet.jl +++ b/examples/IndependentSet.jl @@ -127,6 +127,17 @@ Compose.compose(context(), # One can use [`ConfigsAll`](@ref) to enumerate all sets satisfying the problem constraint. all_independent_sets = solve(problem, ConfigsAll())[] +# It is often difficult to store all configurations in a vector. +# A more clever way to store the data is using the [`TreeConfigEnumerator`](@ref) format. +all_independent_sets_tree = solve(problem, ConfigsAll(; tree_storage=true))[] + +# The results encode the configurations in the sum-product-tree format. One can count and enumerate them explicitly by typing +length(all_independent_sets_tree) + +# + +collect(all_independent_sets_tree) + # To save/read a set of configuration to disk, one can type the following filename = tempname() diff --git a/src/arithematics.jl b/src/arithematics.jl index 8c51ecfb..bf27504a 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -279,21 +279,94 @@ Base.zero(::ConfigSampler{N,S,C}) where {N,S,C} = zero(ConfigSampler{N,S,C}) Base.one(::ConfigSampler{N,S,C}) where {N,S,C} = one(ConfigSampler{N,S,C}) # tree config enumerator -export TreeConfigEnumerator -mutable struct TreeConfigEnumerator{N,S,C} +""" + TreeConfigEnumerator{N,S,C} + +Configuration enumerator encoded in a tree, it is the most natural representation given by a sum-product network +and is often more memory efficient than putting the configurations in a vector. +`N`, `S` and `C` are type parameters from the [`StaticElementVector`](@ref){N,S,C}. + +Fields +----------------------- +* `tag` is one of `ZERO`, `LEAF`, `SUM`, `PROD`. +* `data` is the element stored in a `LEAF` node. +* `left` and `right` are two operands of a `SUM` or `PROD` node. + +Example +------------------------ +```jldoctest; setup=:(using GraphTensorNetworks) +julia> s = TreeConfigEnumerator(bv"00111") +00111 + + +julia> q = TreeConfigEnumerator(bv"10000") +10000 + + +julia> x = s + q ++ +├─ 00111 +└─ 10000 + + +julia> y = x * x +* +├─ + +│ ├─ 00111 +│ └─ 10000 +└─ + + ├─ 00111 + └─ 10000 + + +julia> collect(y) +4-element Vector{StaticBitVector{5, 1}}: + 00111 + 10111 + 10111 + 10000 + +julia> zero(s) + + + +julia> one(s) +00000 + + +``` +""" +struct TreeConfigEnumerator{N,S,C} tag::TreeTag + data::StaticElementVector{N,S,C} left::TreeConfigEnumerator{N,S,C} right::TreeConfigEnumerator{N,S,C} - data::StaticElementVector{N,S,C} - TreeConfigEnumerator(tag::TreeTag, left::TreeConfigEnumerator{N,S,C}, right::TreeConfigEnumerator{N,S,C}) where {N,S,C} = new{N,S,C}(tag, left, right) + TreeConfigEnumerator(tag::TreeTag, left::TreeConfigEnumerator{N,S,C}, right::TreeConfigEnumerator{N,S,C}) where {N,S,C} = new{N,S,C}(tag, zero(StaticElementVector{N,S,C}), left, right) function TreeConfigEnumerator(data::StaticElementVector{N,S,C}) where {N,S,C} - res = new{N,S,C}(LEAF) - res.data = data - return res + new{N,S,C}(LEAF, data) + end + function TreeConfigEnumerator{N,S,C}(tag::TreeTag) where {N,S,C} + @assert tag === ZERO + return new{N,S,C}(tag) end end -children(t::TreeConfigEnumerator) = (t.left, t.right) +# AbstractTree APIs +function children(t::TreeConfigEnumerator) + if isdefined(t, :left) + if isdefined(t, :right) + return [t.left, t.right] + else + return [t.left] + end + else + if isdefined(t, :right) + return [t.right] + else + return typeof(t)[] + end + end +end function printnode(io::IO, t::TreeConfigEnumerator) if t.tag === LEAF print(io, t.data) @@ -319,16 +392,16 @@ function Base.length(x::TreeConfigEnumerator) end function num_nodes(x::TreeConfigEnumerator) - x.tag == ZERO && return 0 + x.tag == ZERO && return 1 x.tag == LEAF && return 1 - return num_nodes(x.left) + num_nodes(x.right) + return num_nodes(x.left) + num_nodes(x.right) + 1 end function Base.:(==)(x::TreeConfigEnumerator{N,S,C}, y::TreeConfigEnumerator{N,S,C}) where {N,S,C} return Set(collect(x)) == Set(collect(y)) end -#Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) +Base.show(io::IO, t::TreeConfigEnumerator) = print_tree(io, t) function Base.collect(x::TreeConfigEnumerator{N,S,C}) where {N,S,C} if x.tag == ZERO @@ -350,7 +423,7 @@ function Base.:*(x::TreeConfigEnumerator{L,S,C}, y::TreeConfigEnumerator{L,S,C}) TreeConfigEnumerator(PROD, x, y) end -Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(ZERO) +Base.zero(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator{N,S,C}(ZERO) Base.one(::Type{TreeConfigEnumerator{N,S,C}}) where {N,S,C} = TreeConfigEnumerator(zero(StaticElementVector{N,S,C})) Base.zero(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = zero(TreeConfigEnumerator{N,S,C}) Base.one(::TreeConfigEnumerator{N,S,C}) where {N,S,C} = one(TreeConfigEnumerator{N,S,C}) diff --git a/src/bounding.jl b/src/bounding.jl index 32703606..99426924 100644 --- a/src/bounding.jl +++ b/src/bounding.jl @@ -28,7 +28,6 @@ function backward_tropical(mode, ixs, @nospecialize(xs::Tuple), iy, @nospecializ mask .= inv.(einsum(EinCode(nixs, niy), nxs, size_dict)) .<= xs[i] .* Tropical(largest_k(mode)-1+1e-12) push!(masks, mask) elseif mode isa SingleConfig - A = zeros(eltype(xs[i]), size(xs[i])) A = einsum(EinCode(nixs, niy), nxs, size_dict) push!(masks, onehotmask(A, xs[i])) else diff --git a/src/configurations.jl b/src/configurations.jl index 6bf0da17..2c9e6cdf 100644 --- a/src/configurations.jl +++ b/src/configurations.jl @@ -8,7 +8,7 @@ Find optimal solutions with bounding. * If `invert` is true, find the minimum. * If `tree_storage` is true, use [`TreeConfigEnumerator`](@ref) as the storage of solutions. """ -function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false, tree_storage::Bool) +function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false, tree_storage::Bool=false) if all && usecuda throw(ArgumentError("ConfigEnumerator can not be computed on GPU!")) end @@ -73,7 +73,7 @@ end Finding all solutions grouped by size. e.g. when the problem is [`MaximalIS`](@ref), it computes all maximal independent sets, or the maximal cliques of it complement. """ -all_solutions(gp::GraphProblem) = solutions(gp, Polynomial{Float64,:x}, all=true, usecuda=false) +all_solutions(gp::GraphProblem) = solutions(gp, Polynomial{Float64,:x}, all=true, usecuda=false, tree_storage=false) # return a mapping from label to onehot bitstrings (degree of freedoms). function fx_solutions(gp::GraphProblem, ::Type{BT}, all::Bool, invert::Bool, tree_storage::Bool) where {BT} diff --git a/src/interfaces.jl b/src/interfaces.jl index ad8acc8a..66b4ce83 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -40,7 +40,7 @@ struct CountingAll <: AbstractProperty end CountingMax{K} <: AbstractProperty CountingMax(K=1) -Counting the number of sets with `K`-largest size. e.g. for [`IndependentSet`](@ref) problem, +Counting the number of sets with largest-K size. e.g. for [`IndependentSet`](@ref) problem, it counts independent sets of size ``\\alpha(G), \\alpha(G)-1, \\ldots, \\alpha(G)-K+1``. * The corresponding tensor element type is [`CountingTropical`](@ref) for `K == 1`, and [`TruncatedPoly`](@ref)`{K}` for `K > 1`. @@ -55,7 +55,7 @@ max_k(::CountingMax{K}) where K = K CountingMin{K} <: AbstractProperty CountingMin(K=1) -Counting the number of sets with `K`-smallest size. +Counting the number of sets with smallest-K size. * The corresponding tensor element type is inverted [`CountingTropical`](@ref) for `K == 1`, and [`TruncatedPoly`](@ref)`{K}` for `K > 1`. * Weighted graph problems is only supported for `K == 1`. @@ -72,16 +72,18 @@ min_k(::CountingMin{K}) where K = K Compute the graph polynomial, e.g. for [`IndependentSet`](@ref) problem, it is the independence polynomial. The `METHOD` type parameter can be one of the following symbols -* `:finitefield`, it uses finite field algebra to fit the polynomial. +Method Argument +--------------------------- +* `:finitefield`, uses finite field algebra to fit the polynomial. * The corresponding tensor element type is [`Mods.Mod`](@ref), * It does not have round-off error, * GPU is supported, * It accepts keyword arguments `maxorder` (optional, e.g. the MIS size in the [`IndependentSet`](@ref) problem). -* `:polynomial`, the program uses polynomial numbers to solve the polynomial directly. +* `:polynomial`, use polynomial numbers to solve the polynomial directly. * The corresponding tensor element type is [`Polynomials.Polynomial`](@ref). * It might have small round-off error depending on the data type for storing the counting. * It has memory overhead that linear to the graph size. -* `:fft`, +* `:fft`, use fast fourier transformation to fit the polynomial. * The corresponding tensor element type is `Base.Complex`. * It has (controllable) round-off error. * BLAS and GPU are supported. @@ -106,6 +108,10 @@ Finding single best solution, e.g. for [`IndependentSet`](@ref) problem, it is o * The corresponding data type is [`CountingTropical{Float64,<:ConfigSampler}`](@ref) if `BOUNDED` is `false`, [`Tropical`](@ref) otherwise. * Weighted graph problems is supported. * GPU is supported, + +Keyword Arguments +---------------------------- +* `bounded`, if it is true, use bounding trick (or boolean gradients) to reduce the working memory to store intermediate configurations. """ struct SingleConfigMax{BOUNDED} <:AbstractProperty end SingleConfigMax(; bounded::Bool=false) = SingleConfigMax{bounded}() @@ -119,6 +125,10 @@ Finding single "worst" solution. * The corresponding data type is inverted [`CountingTropical{Float64,<:ConfigSampler}`](@ref) if `BOUNDED` is `false`, inverted [`Tropical`](@ref) otherwise. * Weighted graph problems is supported. * GPU is supported, + +Keyword Arguments +---------------------------- +* `bounded`, if it is true, use bounding trick (or boolean gradients) to reduce the working memory to store intermediate configurations. """ struct SingleConfigMin{BOUNDED} <:AbstractProperty end SingleConfigMin(; bounded::Bool=false) = SingleConfigMin{bounded}() @@ -131,6 +141,10 @@ Find all valid configurations, e.g. for [`IndependentSet`](@ref) problem, it is * The corresponding data type is [`ConfigEnumerator`](@ref). * Weights do not take effect. + +Keyword Arguments +---------------------------- +* `tree_storage`, if it is true, it uses more memory efficient tree-structure to store the configurations. """ struct ConfigsAll{TREESTORAGE} <:AbstractProperty end ConfigsAll(; tree_storage::Bool=false) = ConfigsAll{tree_storage}() @@ -145,6 +159,11 @@ it is finding all independent sets of sizes ``\\alpha(G), \\alpha(G)-1, \\ldots, * The corresponding data type is [`CountingTropical`](@ref)`{Float64,<:ConfigEnumerator}` for `K == 1` and [`TruncatedPoly`](@ref)`{K,<:ConfigEnumerator}` for `K > 1`. * Weighted graph problems is only supported for `K == 1`. + +Keyword Arguments +---------------------------- +* `bounded`, if it is true, use bounding trick (or boolean gradients) to reduce the working memory to store intermediate configurations. +* `tree_storage`, if it is true, it uses more memory efficient tree-structure to store the configurations. """ struct ConfigsMax{K, BOUNDED, TREESTORAGE} <:AbstractProperty end ConfigsMax(K::Int=1; bounded::Bool=true, tree_storage::Bool=false) = ConfigsMax{K,bounded,tree_storage}() @@ -153,12 +172,17 @@ tree_storage(::ConfigsMax{K,BOUNDED,TREESTORAGE}) where {K,BOUNDED,TREESTORAGE} """ ConfigsMin{K, BOUNDED, TREESTORAGE} <:AbstractProperty - ConfigsMin(K=1; bounded=true, tree_storage::Bool=false) + ConfigsMin(K=1; bounded=true, tree_storage=false) Find configurations with smallest-K sizes. * The corresponding data type is inverted [`CountingTropical`](@ref)`{Float64,<:ConfigEnumerator}` for `K == 1` and inverted [`TruncatedPoly`](@ref)`{K,<:ConfigEnumerator}` for `K > 1`. * Weighted graph problems is only supported for `K == 1`. + +Keyword Arguments +---------------------------- +* `bounded`, if it is true, use bounding trick (or boolean gradients) to reduce the working memory to store intermediate configurations. +* `tree_storage`, if it is true, it uses more memory efficient tree-structure to store the configurations. """ struct ConfigsMin{K, BOUNDED, TREESTORAGE} <:AbstractProperty end ConfigsMin(K::Int=1; bounded::Bool=true, tree_storage::Bool=false) = ConfigsMin{K,bounded, tree_storage}() diff --git a/test/arithematics.jl b/test/arithematics.jl index a45a2d6f..3e1b4383 100644 --- a/test/arithematics.jl +++ b/test/arithematics.jl @@ -38,13 +38,13 @@ end (CountingTropical(5.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(3.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(-3.0, ConfigSampler(StaticBitVector(rand(Bool, 10))))), (CountingTropical(5.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:3])), CountingTropical(3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:4])), CountingTropical(-3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:5]))), (ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:3]), ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:4]), ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:5])), - (TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:3]), - TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:4]), - TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:5]) + (TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...), + TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...), + TreeConfigEnumerator(GraphTensorNetworks.SUM, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...) ), - (TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:3]), - TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:4]), - TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:5]) + (TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...), + TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...), + TreeConfigEnumerator(GraphTensorNetworks.PROD, [TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))) for j=1:2]...) ), (TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))), TreeConfigEnumerator(StaticBitVector(rand(Bool, 10))), @@ -74,6 +74,7 @@ end @test length(a) == 1 @test length(a + a) == 2 @test length(a * a) == 1 + print((a+a) * a) a = ConfigSampler(StaticBitVector(rand(Bool, 10))) @test 1 * a == a From 8cf4136b538ab3bfc86fa030a81fbc8d13b02df6 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 19 Feb 2022 00:14:01 -0500 Subject: [PATCH 8/9] fix docs --- examples/MaximalIS.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/MaximalIS.jl b/examples/MaximalIS.jl index a61feb86..3184b5d1 100644 --- a/examples/MaximalIS.jl +++ b/examples/MaximalIS.jl @@ -71,7 +71,7 @@ counting_min_maximal_independent_set = solve(problem, CountingMin())[] # ##### finding all maximal independent set maximal_configs = solve(problem, ConfigsAll())[] -all(c->is_maximal_independent_set(g, i), maximal_configs) +all(c->is_maximal_independent_set(graph, c), maximal_configs) # @@ -90,7 +90,7 @@ cliques = maximal_cliques(complement(graph)) # ##### finding minimum maximal independent set # It is the [`ConfigsMin`](@ref) property in the program. -minimum_maximal_configs = solve(problem, ConfigsMin())[] +minimum_maximal_configs = solve(problem, ConfigsMin())[].c imgs2 = ntuple(k->show_graph(graph; locs=locations, scale=0.25, From 14da629d83bab93e3458d41e815e8d3d86451dc6 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 19 Feb 2022 00:19:44 -0500 Subject: [PATCH 9/9] fix project.toml --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index c66e2a7b..1393214b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GraphTensorNetworks" uuid = "0978c8c2-34f6-49c7-9826-ea2cc20dabd2" authors = ["GiggleLiu and contributors"] -version = "0.2.1" +version = "0.2.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -39,7 +39,7 @@ SIMDTypes = "0.1" StatsBase = "0.33" TropicalNumbers = "0.4, 0.5" Viznet = "0.3" -AbstractTrees = 0.3 +AbstractTrees = "0.3" julia = "1" [extras]