diff --git a/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl b/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl index ce28d4c..56b2eb4 100644 --- a/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl +++ b/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl @@ -13,8 +13,8 @@ export AbstractBranchingStrategy, NoBranchingStrategy, OptimalBranching export AbstractProblem, AbstractResult, AbstractMeasure, AbstractReducer, AbstractSelector, AbstractPruner, AbstractTableSolver, AbstractSetCoverSolver export NoProblem, NoResult, NoMeasure, NoReducer, NoSelector, NoPruner, NoTableSolver, LPSolver, IPSolver -export apply, measure, reduce!, select, solve_table, prune -export complexity, cover +export apply, measure, reduce, select, solve_table, prune +export complexity, cover, branch include("bitbasis.jl") include("subcover.jl") diff --git a/lib/OptimalBranchingCore/src/bitbasis.jl b/lib/OptimalBranchingCore/src/bitbasis.jl index 6263fb0..a6296dd 100644 --- a/lib/OptimalBranchingCore/src/bitbasis.jl +++ b/lib/OptimalBranchingCore/src/bitbasis.jl @@ -162,9 +162,7 @@ struct BranchingTable{INT <: Integer} bit_length::Int table::Vector{Vector{INT}} end -function BranchingTable(arr::AbstractArray{<:CountingTropical{<:Real, <:ConfigEnumerator{N}}}) where N - return BranchingTable(N, filter(!isempty, vec(map(collect_configs, arr)))) -end + function BranchingTable(n::Int, arr::AbstractVector{<:AbstractVector}) return BranchingTable(n, [_vec2int.(LongLongUInt, x) for x in arr]) end @@ -178,6 +176,7 @@ function Base.show(io::IO, t::BranchingTable{INT}) where INT end end Base.show(io::IO, ::MIME"text/plain", t::BranchingTable) = show(io, t) +Base.copy(t::BranchingTable) = BranchingTable(t.bit_length, copy(t.table)) struct DNF{INT} clauses::Vector{Clause{INT}} diff --git a/lib/OptimalBranchingCore/src/branch.jl b/lib/OptimalBranchingCore/src/branch.jl index f216b4e..528a008 100644 --- a/lib/OptimalBranchingCore/src/branch.jl +++ b/lib/OptimalBranchingCore/src/branch.jl @@ -1,21 +1,27 @@ function optimal_branching(tbl::BranchingTable{INT}, vs::Vector{T}, problem::P, measure::M, solver::S, ::Type{R}; verbose::Bool = false) where{INT, T, P<:AbstractProblem, M<:AbstractMeasure, S<:AbstractSetCoverSolver, R<:AbstractResult} sub_covers = subcovers(tbl) cov, cx = cover(sub_covers, problem, measure, vs, solver; verbose) - branches = Branches([Branch(sub_cover.clause, vs, problem, R) for sub_cover in cov]) + branches = [Branch(sub_cover.clause, vs, problem, R) for sub_cover in cov] return branches end -function solve(p::P, config::SolverConfig) where{P<:AbstractProblem} - reduce!(p, config.reducer) - branches = solve_branches(p, config.branching_strategy) - return sum([(b.problem isa NoProblem) ? b.result : (solve(b.problem, config) * b.result) for b in branches]) +function branch(p::P, config::SolverConfig) where{P<:AbstractProblem} + + (p isa NoProblem) && return zero(config.result_type) + + reduced = reduce(p, config.reducer, config.result_type) + branches = !isnothing(reduced) ? [Branch(reduced[1], reduced[2])] : solve_branches(p, config.branching_strategy, config.result_type) + + return maximum([(branch(b.problem, config) + b.result) for b in branches]) end -function solve_branches(p::P, strategy::OptimalBranching) where{P<:AbstractProblem} +function solve_branches(p::P, strategy::OptimalBranching, result_type::Type{R}) where{P<:AbstractProblem, R<:AbstractResult} + vs = select(p, strategy.measure, strategy.selector) tbl = solve_table(p, strategy.table_solver, vs) pruned_tbl = prune(tbl, strategy.pruner, strategy.measure, p, vs) - branches = optimal_branching(pruned_tbl, vs, p, strategy.measure, strategy.set_cover_solver, strategy.table_result) + branches = optimal_branching(pruned_tbl, vs, p, strategy.measure, strategy.set_cover_solver, result_type) + return branches end diff --git a/lib/OptimalBranchingCore/src/types.jl b/lib/OptimalBranchingCore/src/types.jl index d968c34..131eac3 100644 --- a/lib/OptimalBranchingCore/src/types.jl +++ b/lib/OptimalBranchingCore/src/types.jl @@ -1,10 +1,11 @@ abstract type AbstractProblem end -struct NoProblem end -apply(::NoProblem, ::Clause, ::Vector{T}) where{T} = NoProblem() - +struct NoProblem <: AbstractProblem end abstract type AbstractResult end struct NoResult <: AbstractResult end +apply(::NoProblem, ::Clause, vs) = NoProblem() +result(::NoProblem, ::Clause, vs, ::Type{R}) where{R<:AbstractResult} = NoResult() + # abstract type AbstractBranching end abstract type AbstractMeasure end @@ -13,7 +14,7 @@ function measure(::P, ::NoMeasure) where{P<:AbstractProblem} return 0 end abstract type AbstractReducer end struct NoReducer <: AbstractReducer end -function reduce!(p::P, ::NoReducer) where{P<:AbstractProblem} return p end +function reduce(p::P, ::NoReducer, ::Type{R}) where{P<:AbstractProblem, R<:AbstractResult} return nothing end abstract type AbstractSelector end struct NoSelector <: AbstractSelector end @@ -21,7 +22,7 @@ function select(::P, ::M, ::NoSelector) where{P<:AbstractProblem, M<:AbstractMea abstract type AbstractPruner end struct NoPruner <: AbstractPruner end -prune(bt::BranchingTable, ::NoPruner, ::M, ::P, vs) where{M<:AbstractMeasure, P<:AbstractProblem} = copy(bt) +prune(bt::BranchingTable, ::NoPruner, ::M, ::P, vs) where{M<:AbstractMeasure, P<:AbstractProblem} = bt abstract type AbstractTableSolver end struct NoTableSolver <: AbstractTableSolver end @@ -41,7 +42,7 @@ A struct representing a branching strategy. - `mis::Int`: An integer representing the maximum independent set (MIS) size of the branching strategy. """ -struct Branch{P<:AbstractProblem, R<:AbstractResult} +struct Branch{P<:AbstractProblem, R} problem::P result::R end @@ -64,5 +65,5 @@ end struct SolverConfig{R<:AbstractReducer, B<:AbstractBranchingStrategy, TR<:AbstractResult} reducer::R branching_strategy::B - table_result::Type{TR} -end \ No newline at end of file + result_type::Type{TR} +end diff --git a/lib/OptimalBranchingMIS/Project.toml b/lib/OptimalBranchingMIS/Project.toml index 9956df4..9507083 100644 --- a/lib/OptimalBranchingMIS/Project.toml +++ b/lib/OptimalBranchingMIS/Project.toml @@ -4,17 +4,13 @@ authors = ["Xuanzhao Gao and contributors"] version = "1.0.0-DEV" [deps] -Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" EliminateGraphs = "b3ff564c-d3b6-11e9-0ef2-9b4ae9f9cbe1" -GenericTensorNetworks = "3521c873-ad32-4bb4-b63d-f4f178f42b49" -Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -OptimalBranchingAPI = "fc6b5187-3699-421e-a5a4-8833c3921c85" +OptimalBranchingCore = "c76e7b22-e1d2-40e8-b0f1-f659837787b8" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [compat] -Combinatorics = "1.0.2" EliminateGraphs = "0.2.1" -GenericTensorNetworks = "2.2.0" -Graphs = "1.12.0" +Reexport = "1.2.2" julia = "1.6.7" [extras] diff --git a/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl b/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl index b5245e8..1813603 100644 --- a/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl +++ b/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl @@ -1,8 +1,26 @@ module OptimalBranchingMIS -using OptimalBranchingAPI -using EliminateGraphs, GenericTensorNetworks, GenericTensorNetworks.Graphs +using Reexport +@reexport using OptimalBranchingCore +using EliminateGraphs, EliminateGraphs.Graphs +using OptimalBranchingCore.GenericTensorNetworks +export MISProblem +export MISSize, MISCount +export MISReducer, MinBoundarySelector, EnvFilter, TensorNetworkSolver +export NumOfVertices, D3Measure +export counting_mis1, counting_mis2 + +include("types.jl") +include("graphs.jl") +include("algorithms/mis1.jl") +include("algorithms/mis2.jl") + +include("reducer.jl") +include("selector.jl") +include("tablesolver.jl") +include("pruner.jl") +include("branch.jl") end diff --git a/lib/OptimalBranchingMIS/src/algorithms/mis1.jl b/lib/OptimalBranchingMIS/src/algorithms/mis1.jl new file mode 100644 index 0000000..2d23455 --- /dev/null +++ b/lib/OptimalBranchingMIS/src/algorithms/mis1.jl @@ -0,0 +1,11 @@ +function counting_mis1(eg::EliminateGraph) + N = nv(eg) + if N == 0 + return MISCount(0) + else + vmin, dmin = mindegree_vertex(eg) + return 1 + neighborcover_mapreduce(y->eliminate(counting_mis1, eg, NeighborCover(y)), max, eg, vmin) + end +end + +counting_mis1(g::SimpleGraph) = counting_mis1(EliminateGraph(g)) \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/algorithms/mis2.jl b/lib/OptimalBranchingMIS/src/algorithms/mis2.jl new file mode 100644 index 0000000..43e98a6 --- /dev/null +++ b/lib/OptimalBranchingMIS/src/algorithms/mis2.jl @@ -0,0 +1,110 @@ +using EliminateGraphs: adjacent45 + +function counting_mis2(eg::EliminateGraph) + if nv(eg) == 0 + #@show "0" # CHECKED + return MISCount(0) + elseif nv(eg) == 1 + return MISCount(1) + elseif nv(eg) == 2 + return MISCount(2 - (@inbounds isconnected(eg, eg.vertices[end-1], eg.vertices[end]))) + elseif nv(eg) == 3 + @inbounds a, b, c = eg.vertices[end-2:end] + nedge = isconnected(eg, a, b) + isconnected(eg, a, c) + isconnected(eg, b, c) + if nedge == 0 + return MISCount(3) + elseif nedge == 3 + return MISCount(1) + else + return MISCount(2) + end + else + #@show "1" # CHECKED + vmin, degmin = mindegree_vertex(eg) + if degmin == 0 # DONE + #@show "1.1(1)" # CHECKED + return 1 + eliminate(counting_mis2, eg, vmin) + elseif degmin == 1 # DONE + #@show "1.1(2)" # CHECKED + return 1 + eliminate(counting_mis2, eg, NeighborCover(vmin)) + elseif degmin == 2 + #@show "1.2" # CHECKED + a, b = neighbors(eg, vmin) + if isconnected(eg, a, b) + #@show "1.2.1" # CHECKED + return 1 + eliminate(counting_mis2, eg, NeighborCover(vmin)) + else + #@show "1.2.2" # CHECKED + sn = neighbors2(eg, vmin) + # NOTE: there is no degree one vertex! + if length(sn) == 1 + #@show "1.2.2.1" # CHECKED + w = sn[1] + #return max(2+eliminate(counting_mis2, eg, NeighborCover(w) ∪ Neighbors{CLOSED,2}(vmin)), + #2+eliminate(counting_mis2, eg, Neighbors{CLOSED,2}(vmin)), + # Note: it seems it must choose the latter. Gurantted if one removes one vertex, the MIS is non-increasing. + return 2+eliminate(counting_mis2, eg, (vmin, w, a, b) + ) + else + #@show "1.2.2.2" # CHECKED + return max(1+eliminate(counting_mis2, eg, NeighborCover(vmin)), + eliminate(counting_mis2, eg, MirrorCover(vmin))) + end + end + elseif degmin == 3 # DONE + #@show "1.3" #CHECKED + a, b, c = neighbors(eg, vmin) + nedge = isconnected(eg, a, b) + isconnected(eg, a, c) + isconnected(eg, b, c) + if nedge == 0 + #@show "1.3.1" #CHECKED + ms = mirrorcover(eg, vmin) + if length(ms) > 1 + #@show "1.3.1.1" # CHECKED + return max(1+eliminate(counting_mis2, eg, NeighborCover(vmin)), + eliminate(counting_mis2, eg, ms)) + else + #@show "1.3.1.2" # CHECKED + return max(1+eliminate(counting_mis2, eg, NeighborCover(vmin)), + 2 + eliminate(counting_mis2, eg, NeighborCover(a) ∪ NeighborCover(b)), + 2 + eliminate(counting_mis2, eg, NeighborCover(a) ∪ NeighborCover(c) ∪ Vertex(b)), + 2 + eliminate(counting_mis2, eg, NeighborCover(b) ∪ NeighborCover(c) ∪ Vertex(a)), + ) + end + elseif nedge == 3 + #@show "1.3.2" # CHECKED + return 1 + eliminate(counting_mis2, eg, NeighborCover(vmin)) + else + #@show "1.3.3" # CHECKED + return max(1 + eliminate(counting_mis2, eg, NeighborCover(vmin)), + eliminate(counting_mis2, eg, MirrorCover(vmin))) + end + else # DONE + #@show "1.4" # CHECKED + vmax, degmax = maxdegree_vertex(eg) + if degmax >= 6 # DONE + #@show "1.4.1" + return max(1+eliminate(counting_mis2, eg, NeighborCover(vmax)), + eliminate(counting_mis2, eg, vmax)) + elseif !isconnected(eg) # DONE + #@show "1.4.2" # CHECKED + cluster = find_cluster(eg, vmax) + A = subgraph(eg, cluster) + B = subgraph(eg, setdiff(vertices(eg), cluster)) + return counting_mis2(A) + counting_mis2(B) + elseif degmin == degmax # DONE + #@show "1.4.3" # CHECKED + return max(1+eliminate(counting_mis2, eg, NeighborCover(vmax)), + eliminate(counting_mis2, eg, MirrorCover(vmax))) + else + #@show "1.4.4" # CHECKED + v4, v5 = adjacent45(eg) + return max(1+eliminate(counting_mis2, eg, NeighborCover(v5)), + 1+eliminate(counting_mis2, eg, MirrorCover(v5) ∪ NeighborCover(v4)), + eliminate(counting_mis2, eg, MirrorCover(v5) ∪ Vertex(v4)) + ) + end + end + end +end + +counting_mis2(g::SimpleGraph) = counting_mis2(EliminateGraph(g)) \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/branch.jl b/lib/OptimalBranchingMIS/src/branch.jl new file mode 100644 index 0000000..ef00ccb --- /dev/null +++ b/lib/OptimalBranchingMIS/src/branch.jl @@ -0,0 +1,9 @@ +function OptimalBranchingCore.apply(p::MISProblem, clause::Clause{INT}, vertices::Vector{T}) where {INT<:Integer, T<:Integer} + g = p.g + vertices_removed = removed_vertices(vertices, g, clause) + return MISProblem(remove_vertices(g, vertices_removed)) +end + +function OptimalBranchingCore.result(p::MISProblem, clause::Clause{INT}, vertices::Vector{T}, TR::Type{R}) where {INT<:Integer, R<:AbstractResult, T<:Integer} + return count_ones(clause.val) +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/graphs.jl b/lib/OptimalBranchingMIS/src/graphs.jl new file mode 100644 index 0000000..c9b2ddc --- /dev/null +++ b/lib/OptimalBranchingMIS/src/graphs.jl @@ -0,0 +1,170 @@ +function graph_from_tuples(n::Int, edgs) + g = SimpleGraph(n) + for (i, j) in edgs + add_edge!(g, i, j) + end + g +end + +""" + removed_vertices(vertices::Vector{Int}, g::SimpleGraph, clause::Clause{N}) where N + +Given a list of vertices, a graph, and a clause, this function returns a list of removed vertices. + +The `vertices` argument is a vector of integers representing the vertices to consider. +The `g` argument is a `SimpleGraph` object representing the graph. +The `clause` argument is a `Clause` object representing a clause. + +The function iterates over the `vertices` and checks if the corresponding bit in the `clause.mask` is 1. +If it is, the vertex is added to the list of removed vertices (`rvs`). +If the corresponding bit in the `clause.val` is also 1, the neighbors of the vertex are also added to `rvs`. + +The function returns the list of removed vertices with duplicates removed. +""" +function removed_vertices(vertices::Vector{Int}, g::SimpleGraph, clause::Clause{N}) where N + rvs = Int[] + for (k, v) in enumerate(vertices) + if readbit(clause.mask, k) == 1 + push!(rvs, v) + if readbit(clause.val, k) == 1 + append!(rvs, neighbors(g, v)) + end + end + end + return unique!(rvs) +end + +function remove_vertices(g::SimpleGraph, vertices::Vector{Int}) + g_new, vmap = induced_subgraph(g, setdiff(1:nv(g), vertices)) + return g_new +end + +""" + open_vertices(g::SimpleGraph, vertices::Vector{Int}) + +Remove vertices from the given vector that are connected to all other vertices in the graph. + +# Arguments +- `g::SimpleGraph`: The graph object. +- `vertices::Vector{Int}`: The vector of vertices. + +# Returns +- `Vector{Int}`: The open vertices. + +""" +function open_vertices(g::SimpleGraph, vertices::Vector{Int}) + return unique!([v for v in vertices if !all(x->x ∈ vertices, neighbors(g, v))]) +end + +""" + open_neighbors(g::SimpleGraph, vertices::Vector{Int}) + +Returns a vector of vertices in the graph `g`, which are neighbors of the given vertices and not in the given vertices. + +# Arguments +- `g::SimpleGraph`: The graph in which to find the open neighbors. +- `vertices::Vector{Int}`: The vertices for which to find the open neighbors. + +# Returns +A vector of open neighbors of the given vertices. + +""" +function open_neighbors(g::SimpleGraph, vertices::Vector{Int}) + ov = Vector{Int}() + for v in vertices + for n in neighbors(g, v) + push!(ov, n) + end + end + return unique!(setdiff(ov, vertices)) +end + +""" + closed_neighbors(g::SimpleGraph, vertices::Vector{Int}) + +Returns a set of vertices that includes the input `vertices` as well as their open neighbors. + +# Arguments +- `g::SimpleGraph`: The input graph. +- `vertices::Vector{Int}`: The vertices for which closed neighbors are to be computed. + +# Returns +A set of vertices that includes the input `vertices` as well as their open neighbors. + +""" +function closed_neighbors(g::SimpleGraph, vertices::Vector{Int}) + return vertices ∪ open_neighbors(g, vertices) +end + +""" + neighbor_cover(g::SimpleGraph, v::Int, k::Int) + +Compute the neighbor cover of a vertex in a graph. + +# Arguments +- `g::SimpleGraph`: The input graph. +- `v::Int`: The vertex for which to compute the neighbor cover. +- `k::Int`: The number of iterations to perform. + +# Returns +- `vertices`: An array containing the vertices in the neighbor cover. +- `openvertices`: An array containing the open vertices in the neighbor cover. + +""" +function neighbor_cover(g::SimpleGraph, v::Int, k::Int) + @assert k >= 0 + vertices = [v] + for _ = 1:k + vertices = union(vertices, (neighbors(g, w) for w in vertices)...) + end + openvertices = open_vertices(g, vertices) + return vertices, openvertices +end + +""" + neighbors_2nd(g::SimpleGraph, v::Int) + +Return the second-order neighbors of a vertex `v` in a simple graph `g`. + +# Arguments +- `g::SimpleGraph`: The simple graph. +- `v::Int`: The vertex. + +# Returns +- `Array{Int}`: An array of second-order neighbors of `v`. + +""" +function neighbors_2nd(g::SimpleGraph, v::Int) + return open_neighbors(g, v ∪ neighbors(g, v)) +end + +# vs a subgraph, return N(vs) +function Graphs.neighbors(g::SimpleGraph, vs::Vector{Int}) + set_vs = Set(vs) + set_neighbors = Set{Int}() + for v in vs + neighbors_v = neighbors(g, v) + for n in neighbors_v + if n ∉ set_vs + push!(set_neighbors, n) + end + end + end + return set_neighbors +end + +function folding(g::SimpleGraph, v::Int) + @assert degree(g, v) == 2 + a, b = neighbors(g, v) + if has_edge(g, a, b) + return (induced_subgraph(g, setdiff(1:nv(g), [v, a, b]))[1], 1) + else + # apply the graph rewrite rule + add_vertex!(g) + nn = open_neighbors(g, [v, a, b]) + for n in nn + add_edge!(g, nv(g), n) + end + return (induced_subgraph(g, setdiff(1:nv(g), [v, a, b]))[1], 1) + end +end diff --git a/lib/OptimalBranchingMIS/src/pruner.jl b/lib/OptimalBranchingMIS/src/pruner.jl new file mode 100644 index 0000000..acbbcfc --- /dev/null +++ b/lib/OptimalBranchingMIS/src/pruner.jl @@ -0,0 +1,46 @@ +struct EnvFilter <: AbstractPruner end + +# consider two different branching rule (A, and B) applied on the same set of vertices, with open vertices ovs. +# the neighbors of 1 vertices in A is label as NA1, and the neighbors of 1 vertices in B is label as NB1, and the pink_block is the set of vertices that are not in NB1 but in NA1. +# once mis(A) + mis(pink_block) ≤ mis(B), then A is not a good branching rule, and should be removed. +function OptimalBranchingCore.prune(tbl::BranchingTable{INT}, ::EnvFilter, m::M, p::P, vertices) where{INT<:Integer, M<:AbstractMeasure, P<:AbstractProblem} + g = p.g + openvertices = open_vertices(g, vertices) + ns = neighbors(g, vertices) + so = Set(openvertices) + + new_table = Vector{Vector{INT}}() + + open_vertices_1 = [Int[] for i in 1:length(tbl.table)] + neibs_0 = Set{Int}[] + for i in 1:length(tbl.table) + row = tbl.table[i] + x = row[1] + for n in 1:length(x) + if (x[n] == 1) && (vertices[n] ∈ so) + push!(open_vertices_1[i], vertices[n]) + end + end + push!(neibs_0, setdiff(ns, neighbors(g, open_vertices_1[i]) ∩ ns)) + end + + for i in 1:length(tbl.table) + flag = true + for j in 1:length(tbl.table) + if i != j + pink_block = setdiff(neibs_0[i], neibs_0[j]) + sg_pink, sg_vec = induced_subgraph(g, collect(pink_block)) + mis_pink = mis2(EliminateGraph(sg_pink)) + if (count_ones(tbl.table[i][1]) + mis_pink ≤ count_ones(tbl.table[j][1])) && (!iszero(mis_pink)) + flag = false + break + end + end + end + if flag + push!(new_table, tbl.table[i]) + end + end + + return BranchingTable(OptimalBranchingCore.nbits(tbl), new_table) +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/reducer.jl b/lib/OptimalBranchingMIS/src/reducer.jl new file mode 100644 index 0000000..34d7baf --- /dev/null +++ b/lib/OptimalBranchingMIS/src/reducer.jl @@ -0,0 +1,28 @@ +struct MISReducer <: AbstractReducer end + +function OptimalBranchingCore.reduce(p::MISProblem, ::MISReducer, TR::Type{R}) where R<:AbstractResult + g = p.g + if nv(g) == 0 + return (NoProblem(), 0) + elseif nv(g) == 1 + return (NoProblem(), 1) + elseif nv(g) == 2 + return (NoProblem(), (2 - has_edge(g, 1, 2))) + else + degrees = degree(g) + degmin = minimum(degrees) + vmin = findfirst(==(degmin), degrees) + + if degmin == 0 + all_zero_vertices = findall(==(0), degrees) + return (MISProblem(remove_vertices(g, all_zero_vertices)), (length(all_zero_vertices))) + elseif degmin == 1 + return (MISProblem(remove_vertices(g, neighbors(g, vmin) ∪ vmin)), (1)) + elseif degmin == 2 + g_new, n = folding(g, vmin) + return (MISProblem(g_new), (n)) + end + end + + return nothing +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/selector.jl b/lib/OptimalBranchingMIS/src/selector.jl new file mode 100644 index 0000000..57320e4 --- /dev/null +++ b/lib/OptimalBranchingMIS/src/selector.jl @@ -0,0 +1,28 @@ +""" + struct MinBoundarySelector <: AbstractVertexSelector + +The `MinBoundarySelector` struct represents a strategy for selecting a subgraph with the minimum number of open vertices by k-layers of neighbors. + +# Fields +- `k::Int`: The number of layers of neighbors to consider when selecting the subgraph. + +""" +struct MinBoundarySelector <: AbstractSelector + k::Int # select the subgraph with minimum open vertices by k-layers of neighbors +end + +function OptimalBranchingCore.select(p::MISProblem, m::M, selector::MinBoundarySelector) where{M<:AbstractMeasure} + g = p.g + kneighbor = selector.k + + local vs_min + novs_min = nv(g) + for v in 1:nv(g) + vs, ovs = neighbor_cover(g, v, kneighbor) + if length(ovs) < novs_min + vs_min = vs + novs_min = length(ovs) + end + end + return vs_min +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/tablesolver.jl b/lib/OptimalBranchingMIS/src/tablesolver.jl new file mode 100644 index 0000000..13ab42c --- /dev/null +++ b/lib/OptimalBranchingMIS/src/tablesolver.jl @@ -0,0 +1,44 @@ +function alpha(g::SimpleGraph, openvertices::Vector{Int}) + problem = GenericTensorNetwork(IndependentSet(g); openvertices, optimizer = GreedyMethod(nrepeat=1)) + alpha_tensor = solve(problem, SizeMax()) + return alpha_tensor +end + +# Let us create a function for finding reduced ``\alpha``-tensors." +function reduced_alpha(g::SimpleGraph, openvertices::Vector{Int}) + problem = GenericTensorNetwork(IndependentSet(g); openvertices, optimizer = GreedyMethod(nrepeat=1)) + alpha_tensor = solve(problem, SizeMax()) + return mis_compactify!(alpha_tensor) +end + +function _reduced_alpha_configs(g::SimpleGraph, openvertices::Vector{Int}, potential) + problem = GenericTensorNetwork(IndependentSet(g); openvertices, optimizer = GreedyMethod(nrepeat=1)) + alpha_tensor = solve(problem, SizeMax()) + alpha_configs = solve(problem, ConfigsMax(; bounded=false)) + reduced_alpha_tensor = mis_compactify!(alpha_tensor; potential) + # set the corresponding entries to 0. + alpha_configs[map(iszero, reduced_alpha_tensor)] .= Ref(zero(eltype(alpha_configs))) + # post processing + configs = alpha_configs + return configs +end + +function reduced_alpha_configs(::TensorNetworkSolver, graph::SimpleGraph, openvertices::Vector{Int}, potentials=nothing) + configs = _reduced_alpha_configs(graph, openvertices, potentials) + return BranchingTable(configs) +end + +function OptimalBranchingCore.BranchingTable(arr::AbstractArray{<:CountingTropical{<:Real, <:ConfigEnumerator{N}}}) where N + return BranchingTable(N, filter(!isempty, vec(map(collect_configs, arr)))) +end +# Now we collect these configurations into a vector. +function collect_configs(cfg::CountingTropical{<:Real, <:ConfigEnumerator}, symbols::Union{Nothing, String}=nothing) + cs = cfg.c.data + symbols === nothing ? cs : [String([symbols[i] for (i, v) in enumerate(x) if v == 1]) for x in cs] +end + +function OptimalBranchingCore.solve_table(p::MISProblem, solver::TensorNetworkSolver, vs::Vector{Int}) + ovs = open_vertices(p.g, vs) + subg, vmap = induced_subgraph(p.g, vs) + return reduced_alpha_configs(solver, subg, Int[findfirst(==(v), vs) for v in ovs]) +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl new file mode 100644 index 0000000..87a59ef --- /dev/null +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -0,0 +1,48 @@ +mutable struct MISProblem <: AbstractProblem + g::SimpleGraph +end +copy(p::MISProblem) = MISProblem(copy(p.g)) +Base.show(io::IO, p::MISProblem) = print(io, "MISProblem($(nv(p.g)))") + +struct MISSize <: AbstractResult + mis_size::Int +end + +Base.:+(a::MISSize, b::MISSize) = MISSize(a.mis_size + b.mis_size) +Base.:+(a::MISSize, b::Int) = MISSize(a.mis_size + b) +Base.:+(a::Int, b::MISSize) = MISSize(a + b.mis_size) +Base.max(a::MISSize, b::MISSize) = MISSize(max(a.mis_size, b.mis_size)) +Base.max(a::MISSize, b::Int) = MISSize(max(a.mis_size, b)) +Base.max(a::Int, b::MISSize) = MISSize(max(a, b.mis_size)) +Base.zero(::MISSize) = MISSize(0) +Base.zero(::Type{MISSize}) = MISSize(0) + +struct MISCount <: AbstractResult + mis_size::Int + mis_count::Int + MISCount(mis_size::Int) = new(mis_size, 1) + MISCount(mis_size::Int, mis_count::Int) = new(mis_size, mis_count) +end + +Base.:+(a::MISCount, b::MISCount) = MISCount(a.mis_size + b.mis_size, a.mis_count + b.mis_count) +Base.:+(a::MISCount, b::Int) = MISCount(a.mis_size + b, a.mis_count) +Base.:+(a::Int, b::MISCount) = MISCount(a + b.mis_size, b.mis_count) +Base.max(a::MISCount, b::MISCount) = MISCount(max(a.mis_size, b.mis_size), (a.mis_count + b.mis_count)) +Base.zero(::MISCount) = MISCount(0, 1) +Base.zero(::Type{MISCount}) = MISCount(0, 1) + +struct TensorNetworkSolver <: AbstractTableSolver end + +struct NumOfVertices <: AbstractMeasure end # each vertex is counted as 1 +OptimalBranchingCore.measure(p::MISProblem, ::NumOfVertices) = nv(p.g) + +struct D3Measure <: AbstractMeasure end # n = sum max{d - 2, 0} +function OptimalBranchingCore.measure(p::MISProblem, ::D3Measure) + g = p.g + if nv(g) == 0 + return 0 + else + dg = degree(g) + return Int(sum(max(d - 2, 0) for d in dg)) + end +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/test/branch.jl b/lib/OptimalBranchingMIS/test/branch.jl new file mode 100644 index 0000000..6f4eac4 --- /dev/null +++ b/lib/OptimalBranchingMIS/test/branch.jl @@ -0,0 +1,26 @@ +using OptimalBranchingMIS, EliminateGraphs, EliminateGraphs.Graphs +using Test + +@testset "mis" begin + for n in 20:10:60 + for d in 3:4 + g = random_regular_graph(n, d) + + mis_exact = mis2(EliminateGraph(g)) + p = MISProblem(g) + + for solver in [IPSolver(10), LPSolver(10)], measure in [D3Measure(), NumOfVertices()], pruner in [EnvFilter(), NoPruner()] + bs = OptimalBranching(TensorNetworkSolver(), solver, pruner, MinBoundarySelector(2), measure) + + cfg = SolverConfig(MISReducer(), bs, MISSize) + + cfg_count = SolverConfig(MISReducer(), bs, MISCount) + + res = branch(p, cfg) + res_count = branch(p, cfg_count) + + @test res.mis_size == res_count.mis_size == mis_exact + end + end + end +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/test/runtests.jl b/lib/OptimalBranchingMIS/test/runtests.jl index 8c0e3ba..8fb5daa 100644 --- a/lib/OptimalBranchingMIS/test/runtests.jl +++ b/lib/OptimalBranchingMIS/test/runtests.jl @@ -2,5 +2,5 @@ using OptimalBranchingMIS using Test @testset "OptimalBranchingMIS.jl" begin - # Write your tests here. + include(branch.jl) end