diff --git a/README.md b/README.md index ef36e647..6e6db5d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GraphTensorNetworks [![Build Status](https://github.com/Happy-Diode/GraphTensorNetworks.jl/workflows/CI/badge.svg)](https://github.com/Happy-Diode/GraphTensorNetworks.jl/actions) -[![Coverage Status](https://coveralls.io/repos/github/Happy-Diode/GraphTensorNetworks.jl/badge.svg?branch=fix-test-coverage&t=rIJIK2)](https://coveralls.io/github/Happy-Diode/GraphTensorNetworks.jl?branch=fix-test-coverage) +[![Coverage Status](https://coveralls.io/repos/github/Happy-Diode/GraphTensorNetworks.jl/badge.svg?branch=master&t=rIJIK2)](https://coveralls.io/github/Happy-Diode/GraphTensorNetworks.jl?branch=master) [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://psychic-meme-f4d866f8.pages.github.io/dev/) ## Installation diff --git a/docs/src/index.md b/docs/src/index.md index 415c101f..95de7c6d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,11 +4,20 @@ CurrentModule = GraphTensorNetworks # GraphTensorNetworks +This package uses generic tensor network to compute properties of combinatorial problems defined on graph. +The properties includes the size of the maximum set size, the number of sets of a given size and the enumeration of configurations of a given set size. + ## Background knowledge Please check our paper ["Computing properties of independent sets by generic programming tensor networks"](). -If you find our paper or software useful in your work, please cite us. +If you find our paper or software useful in your work, please cite us: + +```bibtex +(bibtex to be added) +``` ## Quick start -You can find a good installation guide and a quick start in our [README](https://github.com/Happy-Diode/GraphTensorNetworks.jl). \ No newline at end of file +You can find a good installation guide and a quick start in our [README](https://github.com/Happy-Diode/GraphTensorNetworks.jl). + +A good example to start with is the [Independent set problem](@ref). \ No newline at end of file diff --git a/docs/src/ref.md b/docs/src/ref.md index 1814b618..19d3eaa9 100644 --- a/docs/src/ref.md +++ b/docs/src/ref.md @@ -17,7 +17,7 @@ set_packing #### Graph Problem Interfaces ```@docs -generate_tensors +GraphTensorNetworks.generate_tensors symbols flavors get_weights @@ -25,9 +25,17 @@ nflavor ``` To subtype [`GraphProblem`](@ref), the new type must contain a `code` field to represent the (optimized) tensor network. -Interfaces [`generate_tensors`](@ref), [`symbols`](@ref), [`flavors`](@ref) and [`get_weights`] are required. +Interfaces [`GraphTensorNetworks.generate_tensors`](@ref), [`symbols`](@ref), [`flavors`](@ref) and [`get_weights`] are required. [`nflavor`] is optimal. +#### Graph Problem Utilities +```@docs +is_independent_set +mis_compactify! + +cut_size +cut_assign +``` ## Properties ```@docs @@ -81,13 +89,14 @@ MergeGreedy ## Others #### Graph ```@docs -is_independent_set -mis_compactify! show_graph diagonal_coupled_graph square_lattice_graph -unitdisk_graph +unit_disk_graph + +random_diagonal_coupled_graph +random_square_lattice_graph ``` One can also use `random_regular_graph` and `smallgraph` in [Graphs](https://github.com/JuliaGraphs/Graphs.jl) to build special graphs. diff --git a/examples/Coloring/main.jl b/examples/Coloring/main.jl new file mode 100644 index 00000000..3c28aedf --- /dev/null +++ b/examples/Coloring/main.jl @@ -0,0 +1,31 @@ +# # Coloring problem + +# !!! note +# This tutorial only covers the coloring problem specific features, +# It is recommended to read the [Independent set problem](@ref) tutorial too to know more about +# * how to optimize the tensor network contraction order, +# * what are the other graph properties computable, +# * how to select correct method to compute graph properties, +# * how to compute weighted graphs and handle open vertices. + +# ## Introduction +using GraphTensorNetworks, Graphs + +# Please check the docstring of [`Coloring`](@ref) for the definition of the vertex cutting problem. +@doc Coloring + +# In the following, we are going to defined a 3-coloring problem for the Petersen graph. + +graph = Graphs.smallgraph(:petersen) + +# We can visualize this graph using the following function +rot15(a, b, i::Int) = cos(2i*π/5)*a + sin(2i*π/5)*b, cos(2i*π/5)*b - sin(2i*π/5)*a + +locations = [[rot15(0.0, 1.0, i) for i=0:4]..., [rot15(0.0, 0.6, i) for i=0:4]...] + +show_graph(graph; locs=locations) + +# Then we define the cutting problem as +problem = Coloring{3}(graph); + +# ## Solving properties \ No newline at end of file diff --git a/examples/IndependentSet/main.jl b/examples/IndependentSet/main.jl index 4ed0b953..ef9b88b6 100644 --- a/examples/IndependentSet/main.jl +++ b/examples/IndependentSet/main.jl @@ -22,24 +22,36 @@ problem = Independence(graph; optimizer=TreeSA(sc_weight=1.0, ntrials=10, βs=0.01:0.1:15.0, niters=20, rw_weight=0.2), simplifier=MergeGreedy()); +# The `optimizer` is for optimizing the contraction orders. +# Here we use the local search based optimizer in [arXiv:2108.05665](https://arxiv.org/abs/2108.05665). +# If no optimizer is specified, the default fast (in terms of the speed of searching contraction order) +# but worst (in term of contraction complexity) [`GreedyMethod`](@ref) will be used. +# `simplifier` is a preprocessing routine to speed up the `optimizer`. +# Please check section [Tensor Network](@ref) for more details. +# One can check the time, space and read-write complexity with the following function. + +timespacereadwrite_complexity(problem) + +# The return values are `log2` of the the number of iterations, the number elements in the max tensor and the number of read-write operations to tensor elements. + # ## Solving properties # ### Maximum independent set size ``\alpha(G)`` -maximum_independent_set_size = solve(problem, SizeMax()) +maximum_independent_set_size = solve(problem, SizeMax())[] # ### Counting properties # ##### counting all independent sets -count_all_independent_sets = solve(problem, CountingAll()) +count_all_independent_sets = solve(problem, CountingAll())[] # ##### counting independent sets with sizes ``\alpha(G)`` and ``\alpha(G)-1`` -count_max2_independent_sets = solve(problem, CountingMax(2)) +count_max2_independent_sets = solve(problem, CountingMax(2))[] # ##### independence polynomial # For the definition of independence polynomial, please check the docstring of [`Independence`](@ref) or this [wiki page](https://mathworld.wolfram.com/IndependencePolynomial.html). # There are 3 methods to compute a graph polynomial, `:finitefield`, `:fft` and `:polynomial`. # These methods are introduced in the docstring of [`GraphPolynomial`](@ref). -independence_polynomial = solve(problem, GraphPolynomial(; method=:finitefield)) +independence_polynomial = solve(problem, GraphPolynomial(; method=:finitefield))[] # ### Configuration properties # ##### finding one maximum independent set (MIS) @@ -72,4 +84,38 @@ imgs = ntuple(k->(context((k-1)/m, 0.0, 1.2/m, 1.0), show_graph(graph; Compose.set_default_graphic_size(18cm, 4cm); Compose.compose(context(), imgs...) # ##### enumeration of all IS configurations -all_independent_sets = solve(problem, ConfigsAll())[] \ No newline at end of file +all_independent_sets = solve(problem, ConfigsAll())[] + +# To save/read a set of configuration to disk, one can type the following +filename = tempname() + +save_configs(filename, all_independent_sets; format=:binary) + +loaded_sets = load_configs(filename; format=:binary, bitlength=10) + +# !!! note +# When loading data, one needs to provide the `bitlength` if the data is saved in binary format. +# Because the bitstring length is not stored. + +# ## Weights and open vertices +# [`Independence`] accepts weights as a key word argument. +# The following code computes the weighted MIS problem. +problem = Independence(graph; weights=collect(1:10)) + +max_config_weighted = solve(problem, SingleConfigMax())[] + +show_graph(graph; locs=locations, colors= + [iszero(max_config_weighted.c.data[i]) ? "white" : "red" for i=1:nv(graph)]) + +# The following code computes the MIS tropical tensor (reference to be added) with open vertices 1 and 2. +problem = Independence(graph; openvertices=[1,2,3]) + +mis_tropical_tensor = solve(problem, SizeMax()) + +# The MIS tropical tensor shows the MIS size under different configuration of open vertices. +# It is useful in MIS tropical tensor analysis. +# One can compatify this MIS-Tropical tensor by typing + +mis_compactify!(mis_tropical_tensor) + +# It will eliminate some entries having no contribution to the MIS size when embeding this local graph into a larger one. \ No newline at end of file diff --git a/examples/Matching/main.jl b/examples/Matching/main.jl new file mode 100644 index 00000000..af7a5f53 --- /dev/null +++ b/examples/Matching/main.jl @@ -0,0 +1,31 @@ +# # Vertex matching problem + +# !!! note +# This tutorial only covers the vertex matching problem specific features, +# It is recommended to read the [Independent set problem](@ref) tutorial too to know more about +# * how to optimize the tensor network contraction order, +# * what are the other graph properties computable, +# * how to select correct method to compute graph properties, +# * how to compute weighted graphs and handle open vertices. + +# ## Introduction +using GraphTensorNetworks, Graphs + +# Please check the docstring of [`Matching`](@ref) for the definition of the vertex matching problem. +@doc Matching + +# In the following, we are going to defined a matching problem for the Petersen graph. + +graph = Graphs.smallgraph(:petersen) + +# We can visualize this graph using the following function +rot15(a, b, i::Int) = cos(2i*π/5)*a + sin(2i*π/5)*b, cos(2i*π/5)*b - sin(2i*π/5)*a + +locations = [[rot15(0.0, 1.0, i) for i=0:4]..., [rot15(0.0, 0.6, i) for i=0:4]...] + +show_graph(graph; locs=locations) + +# Then we define the matching problem as +problem = Matching(graph); + +# ## Solving properties \ No newline at end of file diff --git a/examples/MaxCut/main.jl b/examples/MaxCut/main.jl index 06bdb8e8..25e47db5 100644 --- a/examples/MaxCut/main.jl +++ b/examples/MaxCut/main.jl @@ -1,7 +1,54 @@ -# # Max-Cut problem +# # Cutting problem (Spin-glass problem) -# ## Problem definition +# !!! note +# This tutorial only covers the cutting problem specific features, +# It is recommended to read the [Independent set problem](@ref) tutorial too to know more about +# * how to optimize the tensor network contraction order, +# * what are the other graph properties computable, +# * how to select correct method to compute graph properties, +# * how to compute weighted graphs and handle open vertices. +# ## Introduction +using GraphTensorNetworks, Graphs + +# Please check the docstring of [`MaxCut`](@ref) for the definition of the vertex cutting problem. +@doc MaxCut + +# In the following, we are going to defined an cutting problem for the Petersen graph. + +graph = Graphs.smallgraph(:petersen) + +# We can visualize this graph using the following function +rot15(a, b, i::Int) = cos(2i*π/5)*a + sin(2i*π/5)*b, cos(2i*π/5)*b - sin(2i*π/5)*a + +locations = [[rot15(0.0, 1.0, i) for i=0:4]..., [rot15(0.0, 0.6, i) for i=0:4]...] + +show_graph(graph; locs=locations) + +# Then we define the cutting problem as +problem = MaxCut(graph); # ## Solving properties -using GraphTensorNetworks, Graphs \ No newline at end of file + +# ### Maximum cut size ``\gamma(G)`` +max_cut_size = solve(problem, SizeMax())[] + +# ### Counting properties +# ##### graph polynomial +# Since the variable ``x`` is defined on edges, +# hence the coefficients of the polynomial is the number of configurations having different number of anti-parallel edges. +max_config = solve(problem, GraphPolynomial())[] + +# ### Configuration properties +# ##### finding one max cut solution +max_edge_config = solve(problem, SingleConfigMax())[] + +# These configurations are defined on edges, we need to find a valid assignment on vertices +max_vertex_config = cut_assign(graph, max_edge_config.c.data) + +max_cut_size_verify = cut_size(graph, max_vertex_config) + +# You should see a consistent result as above `max_cut_size`. + +show_graph(graph; locs=locations, colors=[ + iszero(max_vertex_config[i]) ? "white" : "red" for i=1:nv(graph)]) \ No newline at end of file diff --git a/examples/MaxinalIndependence/main.jl b/examples/MaxinalIndependence/main.jl new file mode 100644 index 00000000..7502f3ec --- /dev/null +++ b/examples/MaxinalIndependence/main.jl @@ -0,0 +1,50 @@ +# # Maximal independent set problem + +# !!! note +# This tutorial only covers the maximal independent set problem specific features, +# It is recommended to read the [Independent set problem](@ref) tutorial too to know more about +# * how to optimize the tensor network contraction order, +# * what are the other graph properties computable, +# * how to select correct method to compute graph properties, +# * how to compute weighted graphs and handle open vertices. + +# ## Introduction +using GraphTensorNetworks, Graphs + +# Please check the docstring of [`MaximalIndependence`](@ref) for the definition of the maximal independence problem. +@doc MaximalIndependence + +# In the following, we are going to defined an cutting problem for the Petersen graph. + +graph = Graphs.smallgraph(:petersen) + +# We can visualize this graph using the following function +rot15(a, b, i::Int) = cos(2i*π/5)*a + sin(2i*π/5)*b, cos(2i*π/5)*b - sin(2i*π/5)*a + +locations = [[rot15(0.0, 1.0, i) for i=0:4]..., [rot15(0.0, 0.6, i) for i=0:4]...] + +show_graph(graph; locs=locations) + +# Then we define the maximal independent set problem as +problem = MaximalIndependence(graph); + +# The tensor network structure is different from that of [`Independence`](@ref), +# Its tensor is defined on a vertex and its neighbourhood, +# and it makes the contraction of [`MaximalIndependence`](@ref) much harder. + +# ## Solving properties + +# ### Counting properties +# ##### maximal independence polynomial +max_config = solve(problem, GraphPolynomial())[] + +# Since it only counts the maximal independent sets, the first several coefficients are 0. + +# ### Configuration properties +# ##### finding all maximal independent set +max_edge_config = solve(problem, ConfigsAll())[] + +# This result should be consistent with that given by the [Bron Kerbosch algorithm](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm) on the complement of Petersen graph. +maximal_cliques = maximal_cliques(complement(graph)) + +# For sparse graphs, the generic tensor network approach is usually much faster and memory efficient. \ No newline at end of file diff --git a/examples/Others/main.jl b/examples/Others/main.jl index c6a9b0d8..1e1c009b 100644 --- a/examples/Others/main.jl +++ b/examples/Others/main.jl @@ -7,7 +7,4 @@ # A vertex cover is a complement of an independent set. # ### Maximal clique problem -# The maximal clique of graph ``G`` is a maximal clique of ``G``'s complement graph. - -# ### Spin-glass problem -# It is another way of saying the Max-Cut problem. \ No newline at end of file +# The maximal clique of graph ``G`` is a maximal clique of ``G``'s complement graph. \ No newline at end of file diff --git a/examples/PaintShop/main.jl b/examples/PaintShop/main.jl new file mode 100644 index 00000000..38a3da8a --- /dev/null +++ b/examples/PaintShop/main.jl @@ -0,0 +1,19 @@ +# # Binary paint shop problem + +# !!! note +# This tutorial only covers the binary paint shop problem specific features, +# It is recommended to read the [Independent set problem](@ref) tutorial too to know more about +# * how to optimize the tensor network contraction order, +# * what are the other graph properties computable, +# * how to select correct method to compute graph properties, +# * how to compute weighted graphs and handle open vertices. + +# ## Introduction +using GraphTensorNetworks, Graphs + +# Please check the docstring of [`PaintShop`](@ref) for the definition of the binary paint shop problem. +@doc PaintShop + +# In the following, we are going to defined a binary paint shop problem for the following string + +sequence = "abaccb" \ No newline at end of file diff --git a/notebooks/tropicalwidget_simple.jl b/notebooks/tropicalwidget_simple.jl index c7fa7c97..758a13ed 100644 --- a/notebooks/tropicalwidget_simple.jl +++ b/notebooks/tropicalwidget_simple.jl @@ -44,7 +44,7 @@ locs = let end # ╔═╡ 9d0ba8b7-5a34-418d-97cf-863f376f8453 -graph = unitdisk_graph(locs, 0.23) # SimpleGraph +graph = unit_disk_graph(locs, 0.23) # SimpleGraph # ╔═╡ 78ee8772-83e4-40fb-8151-0123370481d9 vizconfig(graph; locs=locs, config=rand(Bool, 12), graphsize=8cm) diff --git a/notebooks/tutorial.jl b/notebooks/tutorial.jl index f812105e..c66c60cf 100644 --- a/notebooks/tutorial.jl +++ b/notebooks/tutorial.jl @@ -18,7 +18,7 @@ md"## High level interfaces" locs = [rand(2) for i=1:70]; # ╔═╡ 1522938d-d60b-4133-8625-5a09615a7b26 -g = unitdisk_graph(locs, 0.2) +g = unit_disk_graph(locs, 0.2) # ╔═╡ 05ac5610-4a1d-4433-ba0d-1d13fd8a6733 vizconfig(g; locs=locs, unit=0.5) diff --git a/src/GraphTensorNetworks.jl b/src/GraphTensorNetworks.jl index 64bdfdbe..863299b7 100644 --- a/src/GraphTensorNetworks.jl +++ b/src/GraphTensorNetworks.jl @@ -24,17 +24,17 @@ export contractx, contractf, graph_polynomial, max_size, max_size_count # Graphs export random_regular_graph, diagonal_coupled_graph, is_independent_set -export square_lattice_graph, unitdisk_graph +export square_lattice_graph, unit_disk_graph, random_diagonal_coupled_graph, random_square_lattice_graph # Tensor Networks (Graph problems) export GraphProblem, Independence, MaximalIndependence, Matching, Coloring, optimize_code, set_packing, MaxCut, PaintShop, paintshop_from_pairs, UnWeighted export flavors, symbols, nflavor, get_weights +export mis_compactify!, cut_assign, cut_size # Interfaces export solve, SizeMax, CountingAll, CountingMax, GraphPolynomial, SingleConfigMax, ConfigsAll, ConfigsMax # Utilities -export mis_compactify! export save_configs, load_configs # Visualization diff --git a/src/arithematics.jl b/src/arithematics.jl index 6d55b39d..505fd4c6 100644 --- a/src/arithematics.jl +++ b/src/arithematics.jl @@ -150,10 +150,6 @@ function Base.show(io::IO, ::MIME"text/plain", x::TruncatedPoly{K}) where K end end -# patch for CUDA matmul -Base.:*(a::Bool, y::TruncatedPoly{K,T,TO}) where {K,T,TO} = a ? y : zero(y) -Base.:*(y::TruncatedPoly{K,T,TO}, a::Bool) where {K,T,TO} = a ? y : zero(y) - """ ConfigEnumerator{N,S,C} @@ -276,3 +272,9 @@ onehotv(::Type{ConfigEnumerator{N,S,C}}, i::Integer, v) where {N,S,C} = ConfigEn 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)) + +# Handle boolean, this is a patch for CUDA matmul +for TYPE in [:ConfigEnumerator, :ConfigSampler, :TruncatedPoly] + @eval Base.:*(a::Bool, y::$TYPE) = a ? y : zero(y) + @eval Base.:*(y::$TYPE, a::Bool) = a ? y : zero(y) +end diff --git a/src/graphs.jl b/src/graphs.jl index 7a5b2c70..1e0d08d0 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -2,11 +2,14 @@ using Graphs, OMEinsumContractionOrders import StatsBase """ - is_independent_set(g::SimpleGraph, vertices) + random_square_lattice_graph(m::Int, n::Int, ρ::Real) -Return true if `vertices` is an independent set of graph `g`. +Create a random masked square lattice graph, with number of vertices fixed to ``\\lfloor mn\\rho \\rceil``. """ -is_independent_set(g::SimpleGraph, v) = !any(e->v[e.src] == 1 && v[e.dst] == 1, edges(g)) +function random_square_lattice_graph(m::Int, n::Int, ρ::Real) + @assert ρ >=0 && ρ <= 1 + square_lattice_graph(generate_mask(m, n, round(Int,m*n*ρ))) +end """ square_lattice_graph(mask::AbstractMatrix{Bool}) @@ -15,7 +18,7 @@ Create a masked square lattice graph. """ function square_lattice_graph(mask::AbstractMatrix{Bool}) locs = [(i, j) for i=1:size(mask, 1), j=1:size(mask, 2) if mask[i,j]] - unitdisk_graph(locs, 1.1) + unit_disk_graph(locs, 1.1) end """ @@ -40,15 +43,15 @@ Create a masked diagonal coupled square lattice graph from a specified `mask`. """ function diagonal_coupled_graph(mask::AbstractMatrix{Bool}) locs = [(i, j) for i=1:size(mask, 1), j=1:size(mask, 2) if mask[i,j]] - unitdisk_graph(locs, 1.5) + unit_disk_graph(locs, 1.5) end """ - unitdisk_graph(locs::AbstractVector, unit::Real) + unit_disk_graph(locs::AbstractVector, unit::Real) Create a unit disk graph with locations specified by `locs` and unit distance `unit`. """ -function unitdisk_graph(locs::AbstractVector, unit::Real) +function unit_disk_graph(locs::AbstractVector, unit::Real) n = length(locs) g = SimpleGraph(n) for i=1:n, j=i+1:n diff --git a/src/interfaces.jl b/src/interfaces.jl index c527e891..2178ae38 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -138,14 +138,12 @@ Keyword arguments function solve(gp::GraphProblem, property::AbstractProperty; T=Float64, usecuda=false) if property isa SizeMax syms = symbols(gp) - vertex_index = Dict([s=>i for (i, s) in enumerate(syms)]) - return contractf(x->Tropical{T}.(get_weights(gp, vertex_index[x])), gp; usecuda=usecuda) + return contractf(x->Tropical{T}.(get_weights(gp, x)), gp; usecuda=usecuda) elseif property isa CountingAll return contractx(gp, one(T); usecuda=usecuda) elseif property isa CountingMax{1} syms = symbols(gp) - vertex_index = Dict([s=>i for (i, s) in enumerate(syms)]) - return contractf(x->CountingTropical{T,T}.(get_weights(gp, vertex_index[x])), gp; usecuda=usecuda) + return contractf(x->CountingTropical{T,T}.(get_weights(gp, x)), gp; usecuda=usecuda) elseif property isa CountingMax return contractx(gp, TruncatedPoly(ntuple(i->i == max_k(property) ? one(T) : zero(T), max_k(property)), one(T)); usecuda=usecuda) elseif property isa GraphPolynomial @@ -271,24 +269,4 @@ end # convert to Matrix Base.Matrix(ce::ConfigEnumerator) = plain_matrix(ce) -Base.Vector(ce::StaticElementVector) = collect(ce) - -# some useful API -""" - mis_compactify!(tropicaltensor) - -Compactify tropical tensor for maximum independent set problem. It will eliminate -some entries by setting them to zero, by the criteria that even these entries are removed, the MIS size is not changed. -""" -function mis_compactify!(a::AbstractArray{T}) where T <: TropicalTypes - for (ind_a, val_a) in enumerate(a) - for (ind_b, val_b) in enumerate(a) - bs_a = ind_a - 1 - bs_b = ind_b - 1 - @inbounds if bs_a != bs_b && val_a <= val_b && (bs_b & bs_a) == bs_b - a[ind_a] = zero(T) - end - end - end - return a -end +Base.Vector(ce::StaticElementVector) = collect(ce) \ No newline at end of file diff --git a/src/networks/Independence.jl b/src/networks/Independence.jl index 0bae3554..e7c2de1c 100644 --- a/src/networks/Independence.jl +++ b/src/networks/Independence.jl @@ -10,10 +10,7 @@ In the constructor, `weights` are the weights of vertices. Problem definition --------------------------- -An independent set is defined in the [monadic second order logic](https://digitalcommons.oberlin.edu/cgi/viewcontent.cgi?article=1678&context=honors) as -```math -\\exists x_i,\\ldots,x_M\\left[\\bigwedge_{i\\neq j} (x_i\\neq x_j \\wedge \\neg \\textbf{adj}(x_i, x_j))\\right] -``` +In graph theory, an independent set is a set of vertices in a graph, no two of which are adjacent. Graph polynomial --------------------------- @@ -65,7 +62,7 @@ end flavors(::Type{<:Independence}) = [0, 1] symbols(gp::Independence) = [i for i in 1:gp.nv] -get_weights(gp::Independence, label) = [0, gp.weights isa UnWeighted ? 1 : gp.weights[findfirst(==(label), symbols(gp))]] +get_weights(gp::Independence, label) = [0, gp.weights[findfirst(==(label), symbols(gp))]] # generate tensors function generate_tensors(fx, gp::Independence) @@ -118,3 +115,28 @@ function set_packing(sets; weights=UnWeighted(), openvertices=(), optimizer=Gree Independence(_optimize_code(code, uniformsize(code, 2), optimizer, simplifier), n, weights) end +""" + mis_compactify!(tropicaltensor) + +Compactify tropical tensor for maximum independent set problem. It will eliminate +some entries by setting them to zero, by the criteria that even these entries are removed, the MIS size is not changed. +""" +function mis_compactify!(a::AbstractArray{T}) where T <: TropicalTypes + for (ind_a, val_a) in enumerate(a) + for (ind_b, val_b) in enumerate(a) + bs_a = ind_a - 1 + bs_b = ind_b - 1 + @inbounds if bs_a != bs_b && val_a <= val_b && (bs_b & bs_a) == bs_b + a[ind_a] = zero(T) + end + end + end + return a +end + +""" + is_independent_set(g::SimpleGraph, vertices) + +Return true if `vertices` is an independent set of graph `g`. +""" +is_independent_set(g::SimpleGraph, v) = !any(e->v[e.src] == 1 && v[e.dst] == 1, edges(g)) diff --git a/src/networks/MaxCut.jl b/src/networks/MaxCut.jl index 26c20f33..61da01eb 100644 --- a/src/networks/MaxCut.jl +++ b/src/networks/MaxCut.jl @@ -50,7 +50,7 @@ end flavors(::Type{<:MaxCut}) = [0, 1] symbols(gp::MaxCut) = getixsv(gp.code) -get_weights(gp::MaxCut, label) = [0, gp.weights isa UnWeighted ? 1 : gp.weights[findfirst(==(label), symbols(gp))]] +get_weights(gp::MaxCut, label) = [0, gp.weights[findfirst(==(label), symbols(gp))]] function generate_tensors(fx, gp::MaxCut) ixs = getixsv(gp.code) @@ -62,3 +62,51 @@ function maxcutb(a, b) return [a b; b a] end +""" + cut_assign(g::SimpleGraph, config) + +Returns a valid vertex configurations (a vector of size `nv(g)`) from `config` (an iterator) defined on edges: + +* assign two vertices with the same values if they are connected by an edge with configuration `0`. +* assign two vertices with the different values if they are connected by an edge with configuration `1`. +* error if there is no valid assignment. +""" +function cut_assign(g::SimpleGraph, config) + nv(g) == 0 && return Bool[] + assign = fill(-1, nv(g)) + @inbounds assign[1] = 0 + nassign = 1 + @inbounds while nassign != nv(g) + for (e, c) in zip(edges(g), config) + if assign[e.src] == -1 && assign[e.dst] != -1 + assign[e.src] = assign[e.dst] ⊻ c + elseif assign[e.src] != -1 && assign[e.dst] == -1 + assign[e.dst] = assign[e.src] ⊻ c + elseif assign[e.src] != -1 && assign[e.dst] != -1 + if assign[e.dst] != assign[e.src] ⊻ c + error("Assign conflict on edge $e, current assign is $(assign[e.src]) and $(assign[e.dst]).") + end + end + end + _nassign = count(!=(-1), assign) + if nassign == _nassign + assign[findfirst(==(-1), assign)] = 0 + _nassign += 1 + end + nassign = _nassign + end + return assign +end + +""" + cut_size(g::SimpleGraph, config; weights=UnWeighted()) + +Compute the cut size from vertex `config` (an iterator). +""" +function cut_size(g::SimpleGraph, config; weights=UnWeighted()) + size = zero(eltype(weights)) * false + for (i, e) in enumerate(edges(g)) + size += (config[e.src] != config[e.dst]) * weights[i] + end + return size +end \ No newline at end of file diff --git a/src/networks/MaximalIndependence.jl b/src/networks/MaximalIndependence.jl index aff0a7a2..af1ac03d 100644 --- a/src/networks/MaximalIndependence.jl +++ b/src/networks/MaximalIndependence.jl @@ -48,7 +48,7 @@ end flavors(::Type{<:MaximalIndependence}) = [0, 1] symbols(gp::MaximalIndependence) = [i for i in 1:length(getixsv(gp.code))] -get_weights(gp::MaximalIndependence, label) = [0, gp.weights isa UnWeighted ? 1 : gp.weights[findfirst(==(label), symbols(gp))]] +get_weights(gp::MaximalIndependence, label) = [0, gp.weights[findfirst(==(label), symbols(gp))]] function generate_tensors(fx, mi::MaximalIndependence) ixs = getixsv(mi.code) diff --git a/src/networks/networks.jl b/src/networks/networks.jl index c9413e80..47b733c9 100644 --- a/src/networks/networks.jl +++ b/src/networks/networks.jl @@ -6,6 +6,8 @@ The abstract base type of graph problems. abstract type GraphProblem end struct UnWeighted end +Base.getindex(::UnWeighted, i) = 1 +Base.eltype(::UnWeighted) = Int ######## Interfaces for graph problems ########## """ diff --git a/test/arithematics.jl b/test/arithematics.jl index 1a160f9b..15aab854 100644 --- a/test/arithematics.jl +++ b/test/arithematics.jl @@ -36,7 +36,25 @@ end (CountingTropicalF64(5, 3), CountingTropicalF64(3, 9), CountingTropicalF64(-3, 2)), (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])), ] @test is_commutative_semiring(a, b, c) + @test false * a == zero(a) + @test true * a == a + @test a * false == zero(a) + @test a * true == a end + # the following tests are for Polynomial + ConfigEnumerator + a = ConfigEnumerator([StaticBitVector(trues(10)) for j=1:3]) + @test 1 * a == a + @test 0 * a == zero(a) + @test a[1] == StaticBitVector(trues(10)) + @test copy(a) == a + a = ConfigSampler(StaticBitVector(rand(Bool, 10))) + @test 1 * a == a + @test 0 * a == zero(a) + + println(Max2Poly{Float64,Float64}(1, 1, 1)) + @test abs(Mod{5}(2)) == Mod{5}(2) + @test Mod{5}(12) < Mod{5}(8) end \ No newline at end of file diff --git a/test/configurations.jl b/test/configurations.jl index 30ea8cf0..c24ccec0 100644 --- a/test/configurations.jl +++ b/test/configurations.jl @@ -71,17 +71,6 @@ end @test BitVector(Bool[0,1,1,0,0]) ∈ res.c.data end -@testset "enumerating - max cut" begin - g = SimpleGraph(5) - for (i,j) in [(1,2),(2,3),(3,4),(4,1),(1,5),(2,4)] - add_edge!(g, i, j) - end - code = MaxCut(g; optimizer=GreedyMethod()) - res = best_solutions(code; all=true)[] - @test length(res.c.data) == 2 - @test sum(res.c.data[1]) == 5 -end - @testset "enumerating - coloring" begin g = SimpleGraph(5) for (i,j) in [(1,2),(2,3),(3,4),(4,1),(1,5),(2,4)] diff --git a/test/graph_polynomials.jl b/test/graph_polynomials.jl index f6258894..e2e2ad96 100644 --- a/test/graph_polynomials.jl +++ b/test/graph_polynomials.jl @@ -67,15 +67,6 @@ end @test graph_polynomial(Matching(g), Val(:polynomial))[].coeffs == [6, 90, 145, 75, 15, 1][end:-1:1] end -@testset "spinglass" begin - g = SimpleGraph(5) - for (i,j) in [(1,2),(2,3),(3,4),(4,1),(1,5),(2,4)] - add_edge!(g, i, j) - end - @test graph_polynomial(MaxCut(g), Val(:polynomial))[] == Polynomial([2,2,4,12,10,2]) - @test graph_polynomial(MaxCut(g), Val(:finitefield))[] == Polynomial([2,2,4,12,10,2]) -end - @testset "paint shop" begin labels = collect("abaccb") pb = PaintShop(labels) diff --git a/test/graphs.jl b/test/graphs.jl new file mode 100644 index 00000000..34ff0630 --- /dev/null +++ b/test/graphs.jl @@ -0,0 +1,11 @@ +using GraphTensorNetworks, Test, Graphs + +@testset "special graphs" begin + g = random_square_lattice_graph(10, 10, 0.5) + @test nv(g) == 50 + g = random_diagonal_coupled_graph(10, 10, 0.5) + @test nv(g) == 50 + g = unit_disk_graph([(0.1, 0.2), (0.2, 0.3), (1.2, 1.4)], 1.0) + @test ne(g) == 1 + @test nv(g) == 3 +end \ No newline at end of file diff --git a/test/networks/Independence.jl b/test/networks/Independence.jl new file mode 100644 index 00000000..4755dcca --- /dev/null +++ b/test/networks/Independence.jl @@ -0,0 +1,14 @@ +using GraphTensorNetworks, Test, Graphs + +@testset "mis compactify" begin + g = SimpleGraph(6) + for (i,j) in [(1,2), (2,3), (4,5), (5,6), (1,6)] + add_edge!(g, i, j) + end + g = Independence(g, openvertices=[1,4,6,3]) + m = solve(g, SizeMax()) + @test m isa Array{Tropical{Float64}, 4} + @test count(!iszero, m) == 12 + mis_compactify!(m) + @test count(!iszero, m) == 3 +end \ No newline at end of file diff --git a/test/networks/MaxCut.jl b/test/networks/MaxCut.jl new file mode 100644 index 00000000..4c029475 --- /dev/null +++ b/test/networks/MaxCut.jl @@ -0,0 +1,45 @@ +using GraphTensorNetworks, Test, Graphs + +@testset "graph utils" begin + g2 = SimpleGraph(3) + add_edge!(g2, 1,2) + for g in [smallgraph(:petersen), g2] + gp = MaxCut(g) + mc = max_size(gp) + config = solve(gp, SingleConfigMax())[] + assign = cut_assign(g, config.c.data) + @test assign isa Vector + @test cut_size(g, assign) == mc + end + g = smallgraph(:petersen) + # weighted + weights = collect(1:ne(g)) + gp = MaxCut(g; weights=weights) + mc = max_size(gp) + config = solve(gp, SingleConfigMax())[] + @test solve(gp, CountingMax())[].c == 2 + assign = cut_assign(g, config.c.data) + @test assign isa Vector + @test cut_size(g, assign; weights=weights) == mc +end + +@testset "spinglass" begin + g = SimpleGraph(5) + for (i,j) in [(1,2),(2,3),(3,4),(4,1),(1,5),(2,4)] + add_edge!(g, i, j) + end + @test graph_polynomial(MaxCut(g), Val(:polynomial))[] == Polynomial([2,2,4,12,10,2]) + @test graph_polynomial(MaxCut(g), Val(:finitefield))[] == Polynomial([2,2,4,12,10,2]) +end + +@testset "enumerating - max cut" begin + g = SimpleGraph(5) + for (i,j) in [(1,2),(2,3),(3,4),(4,1),(1,5),(2,4)] + add_edge!(g, i, j) + end + code = MaxCut(g; optimizer=GreedyMethod()) + res = best_solutions(code; all=true)[] + @test length(res.c.data) == 2 + @test sum(res.c.data[1]) == 5 +end + diff --git a/test/networks/networks.jl b/test/networks/networks.jl new file mode 100644 index 00000000..885a2097 --- /dev/null +++ b/test/networks/networks.jl @@ -0,0 +1,2 @@ +include("Independence.jl") +include("MaxCut.jl") \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 34bfd784..56f2f654 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,6 +25,14 @@ end include("interfaces.jl") end +@testset "networks" begin + include("networks/networks.jl") +end + +@testset "graphs" begin + include("graphs.jl") +end + @testset "visualize" begin include("visualize.jl") end