From e338ca1fb6d270d1c8b5b8386d01143bbc71b07b Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Thu, 2 Jan 2025 17:31:23 +0800 Subject: [PATCH 01/12] greedymerge --- .../src/OptimalBranchingCore.jl | 2 +- lib/OptimalBranchingCore/src/branch.jl | 2 +- lib/OptimalBranchingCore/src/greedymerge.jl | 49 +++++++++++++++++++ lib/OptimalBranchingCore/src/setcovering.jl | 2 + .../test/branching_table.jl | 2 +- lib/OptimalBranchingCore/test/greedymerge.jl | 15 ++++++ lib/OptimalBranchingMIS/src/interfaces.jl | 4 +- lib/OptimalBranchingMIS/test/greedymerge.jl | 45 +++++++++++++++++ 8 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 lib/OptimalBranchingCore/src/greedymerge.jl create mode 100644 lib/OptimalBranchingCore/test/greedymerge.jl create mode 100644 lib/OptimalBranchingMIS/test/greedymerge.jl diff --git a/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl b/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl index 30ac60c..8631f23 100644 --- a/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl +++ b/lib/OptimalBranchingCore/src/OptimalBranchingCore.jl @@ -30,5 +30,5 @@ include("interfaces.jl") include("branching_table.jl") include("setcovering.jl") include("branch.jl") - +include("greedymerge.jl") end diff --git a/lib/OptimalBranchingCore/src/branch.jl b/lib/OptimalBranchingCore/src/branch.jl index e69b9e8..df5ea39 100644 --- a/lib/OptimalBranchingCore/src/branch.jl +++ b/lib/OptimalBranchingCore/src/branch.jl @@ -73,7 +73,7 @@ function branch_and_reduce(problem::AbstractProblem, config::BranchingStrategy, variables = select_variables(rp, config.measure, config.selector) # select a subset of variables tbl = branching_table(rp, config.table_solver, variables) # compute the BranchingTable result = optimal_branching_rule(tbl, variables, rp, config.measure, config.set_cover_solver) # compute the optimal branching rule - return sum(result.optimal_rule.clauses) do branch # branch and recurse + return sum(get_clauses(result)) do branch # branch and recurse subproblem, localvalue = apply_branch(rp, branch, variables) branch_and_reduce(subproblem, config, reducer, result_type) * result_type(localvalue) * reducedvalue end diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl new file mode 100644 index 0000000..dbe6e32 --- /dev/null +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -0,0 +1,49 @@ +struct GreedyMerge <: AbstractSetCoverSolver end +function optimal_branching_rule(table::BranchingTable, variables::Vector, problem::AbstractProblem, m::AbstractMeasure, solver::GreedyMerge) + candidates = bit_clauses(table) + return greedymerge(candidates, problem, variables, m) +end + +function bit_clauses(tbl::BranchingTable{INT}) where {INT} + n, bss = tbl.bit_length, tbl.table + temp_clauses = [[Clause(bmask(INT, 1:n), bs) for bs in bss1] for bss1 in bss] + return temp_clauses +end + +function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector,m::AbstractMeasure) where {INT} + active_cls = collect(1:length(cls)) + cls = copy(cls) + merging_pairs = [(i, j) for i in active_cls, j in active_cls if i < j] + n = length(variables) + size_reductions = [size_reduction(problem,m,candidate[1],variables) for candidate in cls] + γ = complexity_bv(size_reductions) + while !isempty(merging_pairs) + i, j = popfirst!(merging_pairs) + if i in active_cls && j in active_cls + for ii in 1:length(cls[i]), jj in 1:length(cls[j]) + if bdistance(cls[i][ii], cls[j][jj]) == 1 + cl12 = gather2(n, cls[i][ii], cls[j][jj]) + l12 = size_reduction(problem,m,cl12,variables) + if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-8 + push!(cls, [cl12]) + k = length(cls) + deleteat!(active_cls, findfirst(==(i), active_cls)) + deleteat!(active_cls, findfirst(==(j), active_cls)) + for ii in active_cls + push!(merging_pairs, (ii, k)) + end + push!(active_cls, k) + push!(size_reductions, l12) + γ = complexity_bv(size_reductions[active_cls]) + break + end + end + end + end + end + return [cl[1] for cl in cls[active_cls]] +end + +function size_reduction(p::AbstractProblem, m::AbstractMeasure, cl::Clause{INT}, variables::Vector) where {INT} + return measure(p, m) - measure(first(apply_branch(p, cl, variables)), m) +end \ No newline at end of file diff --git a/lib/OptimalBranchingCore/src/setcovering.jl b/lib/OptimalBranchingCore/src/setcovering.jl index bc58b82..1c0ec0c 100644 --- a/lib/OptimalBranchingCore/src/setcovering.jl +++ b/lib/OptimalBranchingCore/src/setcovering.jl @@ -113,6 +113,8 @@ struct OptimalBranchingResult{INT <: Integer, T <: Real} γ::Float64 end Base.show(io::IO, results::OptimalBranchingResult{INT, T}) where {INT, T} = print(io, "OptimalBranchingResult{$INT, $T}:\n selected_ids: $(results.selected_ids)\n optimal_rule: $(results.optimal_rule)\n branching_vector: $(results.branching_vector)\n γ: $(results.γ)") +get_clauses(results::OptimalBranchingResult) = results.optimal_rule.clauses +get_clauses(res::AbstractArray) = res """ minimize_γ(table::BranchingTable, candidates::Vector{Clause}, Δρ::Vector, solver) diff --git a/lib/OptimalBranchingCore/test/branching_table.jl b/lib/OptimalBranchingCore/test/branching_table.jl index 7c2e0b5..468eeaf 100644 --- a/lib/OptimalBranchingCore/test/branching_table.jl +++ b/lib/OptimalBranchingCore/test/branching_table.jl @@ -1,5 +1,5 @@ using OptimalBranchingCore, GenericTensorNetworks -using BitBasis +using OptimalBranchingCore.BitBasis using Test @testset "branching table" begin diff --git a/lib/OptimalBranchingCore/test/greedymerge.jl b/lib/OptimalBranchingCore/test/greedymerge.jl new file mode 100644 index 0000000..4de7b09 --- /dev/null +++ b/lib/OptimalBranchingCore/test/greedymerge.jl @@ -0,0 +1,15 @@ +using Test +using OptimalBranchingCore +using OptimalBranchingCore: bit_clauses +using OptimalBranchingCore.BitBasis +using GenericTensorNetworks + +@testset "bit_clauses" begin + tbl = BranchingTable(5, [ + [StaticElementVector(2, [0, 0, 1, 0, 0]), StaticElementVector(2, [0, 1, 0, 0, 0])], + [StaticElementVector(2, [1, 0, 0, 1, 0])], + [StaticElementVector(2, [0, 0, 1, 0, 1])], + ]) + + bc = bit_clauses(tbl) +end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/interfaces.jl b/lib/OptimalBranchingMIS/src/interfaces.jl index de29ab0..50d493a 100644 --- a/lib/OptimalBranchingMIS/src/interfaces.jl +++ b/lib/OptimalBranchingMIS/src/interfaces.jl @@ -30,8 +30,8 @@ Calculate the size and the number of branches of the Maximum Independent Set (MI ### Returns - A tuple `(size, count)` where `size` is the size of the Maximum Independent Set and `count` is the number of branches. """ -function mis_branch_count(g::AbstractGraph; branching_strategy::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) +function mis_branch_count(g::AbstractGraph; bs::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) p = MISProblem(g) - res = branch_and_reduce(p, branching_strategy, reducer, MaxSizeBranchCount) + res = branch_and_reduce(p, bs, reducer, MaxSizeBranchCount) return (res.size, res.count) end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/test/greedymerge.jl b/lib/OptimalBranchingMIS/test/greedymerge.jl new file mode 100644 index 0000000..a4395d9 --- /dev/null +++ b/lib/OptimalBranchingMIS/test/greedymerge.jl @@ -0,0 +1,45 @@ +using OptimalBranchingMIS +using OptimalBranchingMIS.EliminateGraphs.Graphs +using Test +using Random +using OptimalBranchingCore +using OptimalBranchingCore.BitBasis +using GenericTensorNetworks +using OptimalBranchingCore: bit_clauses +Random.seed!(1234) + +@testset "GreedyMerge" begin + edges = [(1, 4), (1, 5), (3, 4), (2, 5), (4, 5), (1, 6), (2, 7), (3, 8)] + example_g = SimpleGraph(Graphs.SimpleEdge.(edges)) + p = MISProblem(example_g) + tbl = BranchingTable(5, [ + [StaticElementVector(2, [0, 0, 0, 0, 1]), StaticElementVector(2, [0, 0, 0, 1, 0])], + [StaticElementVector(2, [0, 0, 1, 0, 1])], + [StaticElementVector(2, [0, 1, 0, 1, 0])], + [StaticElementVector(2, [1, 1, 1, 0, 0])], + ]) + cls = bit_clauses(tbl) + clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + @test clsf[1].mask == cls[3][1].mask + @test clsf[1].val == cls[3][1].val + @test clsf[2].mask == cls[4][1].mask + @test clsf[2].val == cls[4][1].val + @test clsf[3].mask == 27 + @test clsf[3].val == 16 +end + +@testset "mis" begin + num = 10 + for _ in 1:num + g = random_regular_graph(60, 3) + + bs = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = D3Measure(), set_cover_solver = OptimalBranchingCore.GreedyMerge()) + mis1,count1 = mis_branch_count(g; bs) + mis2,count2 = mis_branch_count(g) + if mis1 != mis2 + println("g") + end + @show count1,count2 + end + +end \ No newline at end of file From 82d019de0c0e401008b339aaab5b7d622c45f476 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Thu, 2 Jan 2025 20:42:40 +0800 Subject: [PATCH 02/12] size_reduction --- lib/OptimalBranchingCore/src/branch.jl | 2 +- lib/OptimalBranchingCore/src/greedymerge.jl | 3 +++ lib/OptimalBranchingMIS/src/types.jl | 16 ++++++++++++++++ lib/OptimalBranchingMIS/test/greedymerge.jl | 12 +++++------- lib/OptimalBranchingMIS/test/types.jl | 15 +++++++++++++++ 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 lib/OptimalBranchingMIS/test/types.jl diff --git a/lib/OptimalBranchingCore/src/branch.jl b/lib/OptimalBranchingCore/src/branch.jl index df5ea39..cbdc3f4 100644 --- a/lib/OptimalBranchingCore/src/branch.jl +++ b/lib/OptimalBranchingCore/src/branch.jl @@ -15,7 +15,7 @@ A [`OptimalBranchingResult`](@ref) object representing the optimal branching rul """ function optimal_branching_rule(table::BranchingTable, variables::Vector, problem::AbstractProblem, m::AbstractMeasure, solver::AbstractSetCoverSolver) candidates = candidate_clauses(table) - size_reductions = [measure(problem, m) - measure(first(apply_branch(problem, candidate, variables)), m) for candidate in candidates] + size_reductions = [size_reduction(problem,m,candidate,variables) for candidate in candidates] return minimize_γ(table, candidates, size_reductions, solver; γ0=2.0) end diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl index dbe6e32..4d8411d 100644 --- a/lib/OptimalBranchingCore/src/greedymerge.jl +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -23,6 +23,9 @@ function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, for ii in 1:length(cls[i]), jj in 1:length(cls[j]) if bdistance(cls[i][ii], cls[j][jj]) == 1 cl12 = gather2(n, cls[i][ii], cls[j][jj]) + if cl12.mask == 0 + continue + end l12 = size_reduction(problem,m,cl12,variables) if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-8 push!(cls, [cl12]) diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl index 7a1f44f..8b0fdc5 100644 --- a/lib/OptimalBranchingMIS/src/types.jl +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -83,4 +83,20 @@ function OptimalBranchingCore.measure(p::MISProblem, ::D3Measure) dg = degree(g) return Int(sum(max(d - 2, 0) for d in dg)) end +end + +function OptimalBranchingCore.size_reduction(p::MISProblem, m::D3Measure, cl::Clause{INT}, variables::Vector) where {INT} + vertices_removed = removed_vertices(variables, p.g, cl) + sum = 0 + for v in vertices_removed + sum += max(degree(p.g, v) - 2, 0) + end + vertices_removed_neighbors = setdiff(mapreduce( v->neighbors(p.g, v),∪,vertices_removed), vertices_removed) + for v in vertices_removed_neighbors + sum += max(degree(p.g, v) - 2 ) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed,neighbors(p.g, v)), 0) + end + # @show sum + # old_sum = measure(p, m) - measure(first(OptimalBranchingCore.apply_branch(p, cl, variables)), m) + # old_sum != sum && @show old_sum + return sum end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/test/greedymerge.jl b/lib/OptimalBranchingMIS/test/greedymerge.jl index a4395d9..ea211c4 100644 --- a/lib/OptimalBranchingMIS/test/greedymerge.jl +++ b/lib/OptimalBranchingMIS/test/greedymerge.jl @@ -29,17 +29,15 @@ Random.seed!(1234) end @testset "mis" begin - num = 10 - for _ in 1:num - g = random_regular_graph(60, 3) - + for _num in 60:10:100 + g = random_regular_graph(_num, 3) + reducer = NoReducer() bs = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = D3Measure(), set_cover_solver = OptimalBranchingCore.GreedyMerge()) - mis1,count1 = mis_branch_count(g; bs) - mis2,count2 = mis_branch_count(g) + mis1,count1 = mis_branch_count(g; bs, reducer) + mis2,count2 = mis_branch_count(g;reducer) if mis1 != mis2 println("g") end @show count1,count2 end - end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/test/types.jl b/lib/OptimalBranchingMIS/test/types.jl new file mode 100644 index 0000000..5b97785 --- /dev/null +++ b/lib/OptimalBranchingMIS/test/types.jl @@ -0,0 +1,15 @@ +using OptimalBranchingMIS +using OptimalBranchingMIS.EliminateGraphs.Graphs +using Test +using Random +using OptimalBranchingCore +using OptimalBranchingCore.BitBasis +using GenericTensorNetworks +using OptimalBranchingCore: size_reduction + +@testset "size_reduction" begin + g = random_regular_graph(60, 3) + vs = collect(1:20) + cl = Clause(bit"1111111111", bit"1011010111") + size_reduction(MISProblem(g),D3Measure(),cl,vs) +end \ No newline at end of file From b385d22858166cd2d5d402ab0efaad8831eb2599 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 14:01:52 +0800 Subject: [PATCH 03/12] size_reduction --- lib/OptimalBranchingMIS/src/types.jl | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl index 8b0fdc5..0ced8c3 100644 --- a/lib/OptimalBranchingMIS/src/types.jl +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -1,5 +1,5 @@ """ - mutable struct MISProblem <: AbstractProblem + mutable struct MISProblem <: AbstractProblem Represents a Maximum Independent Set (MIS) problem. @@ -12,25 +12,25 @@ Represents a Maximum Independent Set (MIS) problem. """ mutable struct MISProblem <: AbstractProblem - g::SimpleGraph + g::SimpleGraph{Int} end Base.copy(p::MISProblem) = MISProblem(copy(p.g)) Base.show(io::IO, p::MISProblem) = print(io, "MISProblem($(nv(p.g)))") Base.isempty(p::MISProblem) = nv(p.g) == 0 """ - TensorNetworkSolver - TensorNetworkSolver(; prune_by_env::Bool = true) + TensorNetworkSolver + TensorNetworkSolver(; prune_by_env::Bool = true) A struct representing a solver for tensor network problems. This struct serves as a specific implementation of the `AbstractTableSolver` type. """ @kwdef struct TensorNetworkSolver <: AbstractTableSolver - prune_by_env::Bool = true + prune_by_env::Bool = true end """ - NumOfVertices + NumOfVertices A struct representing a measure that counts the number of vertices in a graph. Each vertex is counted as 1. @@ -41,7 +41,7 @@ Each vertex is counted as 1. struct NumOfVertices <: AbstractMeasure end """ - measure(p::MISProblem, ::NumOfVertices) + measure(p::MISProblem, ::NumOfVertices) Calculates the number of vertices in the given `MISProblem`. @@ -54,7 +54,7 @@ Calculates the number of vertices in the given `MISProblem`. OptimalBranchingCore.measure(p::MISProblem, ::NumOfVertices) = nv(p.g) """ - D3Measure + D3Measure A struct representing a measure that calculates the sum of the maximum degree minus 2 for each vertex in the graph. @@ -64,7 +64,7 @@ A struct representing a measure that calculates the sum of the maximum degree mi struct D3Measure <: AbstractMeasure end """ - measure(p::MISProblem, ::D3Measure) + measure(p::MISProblem, ::D3Measure) Calculates the D3 measure for the given `MISProblem`, which is defined as the sum of the maximum degree of each vertex minus 2, for all vertices in the graph. @@ -76,27 +76,27 @@ the maximum degree of each vertex minus 2, for all vertices in the graph. - `Int`: The computed D3 measure value. """ 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 + 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 function OptimalBranchingCore.size_reduction(p::MISProblem, m::D3Measure, cl::Clause{INT}, variables::Vector) where {INT} - vertices_removed = removed_vertices(variables, p.g, cl) - sum = 0 - for v in vertices_removed - sum += max(degree(p.g, v) - 2, 0) - end - vertices_removed_neighbors = setdiff(mapreduce( v->neighbors(p.g, v),∪,vertices_removed), vertices_removed) - for v in vertices_removed_neighbors - sum += max(degree(p.g, v) - 2 ) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed,neighbors(p.g, v)), 0) - end - # @show sum - # old_sum = measure(p, m) - measure(first(OptimalBranchingCore.apply_branch(p, cl, variables)), m) - # old_sum != sum && @show old_sum - return sum -end \ No newline at end of file + vertices_removed = removed_vertices(variables, p.g, cl) + sum = 0 + for v in vertices_removed + sum += max(degree(p.g, v) - 2, 0) + end + vertices_removed_neighbors = setdiff(mapreduce(v -> neighbors(p.g, v), ∪, vertices_removed), vertices_removed) + for v in vertices_removed_neighbors + sum += max(degree(p.g, v) - 2) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed, neighbors(p.g, v)), 0) + end + # @show sum + # old_sum = measure(p, m) - measure(first(OptimalBranchingCore.apply_branch(p, cl, variables)), m) + # old_sum != sum && @show old_sum + return sum +end From 7198b1896112bed9668a13d972e9ed86a5eb275d Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 17:21:00 +0800 Subject: [PATCH 04/12] spaces --- lib/OptimalBranchingCore/src/greedymerge.jl | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl index 4d8411d..7fd164a 100644 --- a/lib/OptimalBranchingCore/src/greedymerge.jl +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -10,23 +10,23 @@ function bit_clauses(tbl::BranchingTable{INT}) where {INT} return temp_clauses end -function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector,m::AbstractMeasure) where {INT} +function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector, m::AbstractMeasure) where {INT} active_cls = collect(1:length(cls)) cls = copy(cls) merging_pairs = [(i, j) for i in active_cls, j in active_cls if i < j] - n = length(variables) - size_reductions = [size_reduction(problem,m,candidate[1],variables) for candidate in cls] - γ = complexity_bv(size_reductions) + n = length(variables) + size_reductions = [size_reduction(problem, m, candidate[1], variables) for candidate in cls] + γ = complexity_bv(size_reductions) while !isempty(merging_pairs) i, j = popfirst!(merging_pairs) if i in active_cls && j in active_cls for ii in 1:length(cls[i]), jj in 1:length(cls[j]) if bdistance(cls[i][ii], cls[j][jj]) == 1 cl12 = gather2(n, cls[i][ii], cls[j][jj]) - if cl12.mask == 0 - continue - end - l12 = size_reduction(problem,m,cl12,variables) + if cl12.mask == 0 + continue + end + l12 = size_reduction(problem, m, cl12, variables) if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-8 push!(cls, [cl12]) k = length(cls) @@ -37,16 +37,16 @@ function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, end push!(active_cls, k) push!(size_reductions, l12) - γ = complexity_bv(size_reductions[active_cls]) + γ = complexity_bv(size_reductions[active_cls]) break end end end end end - return [cl[1] for cl in cls[active_cls]] + return [cl[1] for cl in cls[active_cls]] end function size_reduction(p::AbstractProblem, m::AbstractMeasure, cl::Clause{INT}, variables::Vector) where {INT} - return measure(p, m) - measure(first(apply_branch(p, cl, variables)), m) -end \ No newline at end of file + return measure(p, m) - measure(first(apply_branch(p, cl, variables)), m) +end From 129efa5ef0933d9b40071f54ba896c701afd90ff Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 17:44:01 +0800 Subject: [PATCH 05/12] sapces --- lib/OptimalBranchingCore/src/branch.jl | 27 ++++++---- lib/OptimalBranchingMIS/src/interfaces.jl | 18 +++---- lib/OptimalBranchingMIS/src/types.jl | 55 ++++++++++---------- lib/OptimalBranchingMIS/test/greedymerge.jl | 57 ++++++++++----------- lib/OptimalBranchingMIS/test/types.jl | 8 +-- 5 files changed, 84 insertions(+), 81 deletions(-) diff --git a/lib/OptimalBranchingCore/src/branch.jl b/lib/OptimalBranchingCore/src/branch.jl index cbdc3f4..9c2d8b4 100644 --- a/lib/OptimalBranchingCore/src/branch.jl +++ b/lib/OptimalBranchingCore/src/branch.jl @@ -15,10 +15,15 @@ A [`OptimalBranchingResult`](@ref) object representing the optimal branching rul """ function optimal_branching_rule(table::BranchingTable, variables::Vector, problem::AbstractProblem, m::AbstractMeasure, solver::AbstractSetCoverSolver) candidates = candidate_clauses(table) - size_reductions = [size_reduction(problem,m,candidate,variables) for candidate in candidates] - return minimize_γ(table, candidates, size_reductions, solver; γ0=2.0) + size_reductions = [size_reduction(problem, m, candidate, variables) for candidate in candidates] + return minimize_γ(table, candidates, size_reductions, solver; γ0 = 2.0) end +function size_reduction(p::AbstractProblem, m::AbstractMeasure, cl::Clause{INT}, variables::Vector) where {INT} + return measure(p, m) - measure(first(apply_branch(p, cl, variables)), m) +end + + """ BranchingStrategy BranchingStrategy(; kwargs...) @@ -31,20 +36,20 @@ A struct representing the configuration for a solver, including the reducer and - `selector::AbstractSelector`: The selector to select the next branching variable or decision. - `m::AbstractMeasure`: The measure to evaluate the performance of the branching strategy. """ -@kwdef struct BranchingStrategy{TS<:AbstractTableSolver, SCS<:AbstractSetCoverSolver, SL<:AbstractSelector, M<:AbstractMeasure} +@kwdef struct BranchingStrategy{TS <: AbstractTableSolver, SCS <: AbstractSetCoverSolver, SL <: AbstractSelector, M <: AbstractMeasure} set_cover_solver::SCS = IPSolver() table_solver::TS selector::SL measure::M end -Base.show(io::IO, config::BranchingStrategy) = print(io, -""" -BranchingStrategy -├── table_solver - $(config.table_solver) -├── set_cover_solver - $(config.set_cover_solver) -├── selector - $(config.selector) -└── measure - $(config.measure) -""") +Base.show(io::IO, config::BranchingStrategy) = print(io, + """ + BranchingStrategy + ├── table_solver - $(config.table_solver) + ├── set_cover_solver - $(config.set_cover_solver) + ├── selector - $(config.selector) + └── measure - $(config.measure) + """) """ branch_and_reduce(problem::AbstractProblem, config::BranchingStrategy; reducer::AbstractReducer=NoReducer(), result_type=Int) diff --git a/lib/OptimalBranchingMIS/src/interfaces.jl b/lib/OptimalBranchingMIS/src/interfaces.jl index 50d493a..83e397a 100644 --- a/lib/OptimalBranchingMIS/src/interfaces.jl +++ b/lib/OptimalBranchingMIS/src/interfaces.jl @@ -1,37 +1,37 @@ """ - mis_size(g::AbstractGraph; bs::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer::AbstractReducer = MISReducer()) + mis_size(g::AbstractGraph; branching_strategy::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer::AbstractReducer = MISReducer()) Calculate the size of the Maximum Independent Set (MIS) for a given graph. ### Arguments - `g::AbstractGraph`: The graph for which the MIS size is to be calculated. -- `bs::BranchingStrategy`: (optional) The branching strategy to be used. Defaults to a strategy using `table_solver=TensorNetworkSolver`, `selector=MinBoundaryHighDegreeSelector(2, 6, 0)`, and `measure=D3Measure`. +- `branching_strategy::BranchingStrategy`: (optional) The branching strategy to be used. Defaults to a strategy using `table_solver=TensorNetworkSolver`, `selector=MinBoundaryHighDegreeSelector(2, 6, 0)`, and `measure=D3Measure`. - `reducer::AbstractReducer`: (optional) The reducer to be applied. Defaults to `MISReducer`. ### Returns - An integer representing the size of the Maximum Independent Set for the given graph. """ -function mis_size(g::AbstractGraph; bs::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) +function mis_size(g::AbstractGraph; branching_strategy::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = D3Measure()), reducer = MISReducer()) p = MISProblem(g) - res = branch_and_reduce(p, bs, reducer, MaxSize) + res = branch_and_reduce(p, branching_strategy, reducer, MaxSize) return res.size end """ - mis_branch_count(g::AbstractGraph; bs::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) + mis_branch_count(g::AbstractGraph; branching_strategy::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) Calculate the size and the number of branches of the Maximum Independent Set (MIS) for a given graph. ### Arguments - `g::AbstractGraph`: The graph for which the MIS size and the number of branches are to be calculated. -- `bs::BranchingStrategy`: (optional) The branching strategy to be used. Defaults to a strategy using `table_solver=TensorNetworkSolver`, `selector=MinBoundaryHighDegreeSelector(2, 6, 0)`, and `measure=D3Measure`. +- `branching_strategy::BranchingStrategy`: (optional) The branching strategy to be used. Defaults to a strategy using `table_solver=TensorNetworkSolver`, `selector=MinBoundaryHighDegreeSelector(2, 6, 0)`, and `measure=D3Measure`. - `reducer::AbstractReducer`: (optional) The reducer to be applied. Defaults to `MISReducer`. ### Returns - A tuple `(size, count)` where `size` is the size of the Maximum Independent Set and `count` is the number of branches. """ -function mis_branch_count(g::AbstractGraph; bs::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure=D3Measure()), reducer=MISReducer()) +function mis_branch_count(g::AbstractGraph; branching_strategy::BranchingStrategy = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = D3Measure()), reducer = MISReducer()) p = MISProblem(g) - res = branch_and_reduce(p, bs, reducer, MaxSizeBranchCount) + res = branch_and_reduce(p, branching_strategy, reducer, MaxSizeBranchCount) return (res.size, res.count) -end \ No newline at end of file +end diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl index 0ced8c3..4f64776 100644 --- a/lib/OptimalBranchingMIS/src/types.jl +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -1,5 +1,5 @@ """ - mutable struct MISProblem <: AbstractProblem +mutable struct MISProblem <: AbstractProblem Represents a Maximum Independent Set (MIS) problem. @@ -12,25 +12,25 @@ Represents a Maximum Independent Set (MIS) problem. """ mutable struct MISProblem <: AbstractProblem - g::SimpleGraph{Int} + g::SimpleGraph{Int} end Base.copy(p::MISProblem) = MISProblem(copy(p.g)) Base.show(io::IO, p::MISProblem) = print(io, "MISProblem($(nv(p.g)))") Base.isempty(p::MISProblem) = nv(p.g) == 0 """ - TensorNetworkSolver - TensorNetworkSolver(; prune_by_env::Bool = true) +TensorNetworkSolver +TensorNetworkSolver(; prune_by_env::Bool = true) A struct representing a solver for tensor network problems. This struct serves as a specific implementation of the `AbstractTableSolver` type. """ @kwdef struct TensorNetworkSolver <: AbstractTableSolver - prune_by_env::Bool = true + prune_by_env::Bool = true end """ - NumOfVertices +NumOfVertices A struct representing a measure that counts the number of vertices in a graph. Each vertex is counted as 1. @@ -41,7 +41,7 @@ Each vertex is counted as 1. struct NumOfVertices <: AbstractMeasure end """ - measure(p::MISProblem, ::NumOfVertices) +measure(p::MISProblem, ::NumOfVertices) Calculates the number of vertices in the given `MISProblem`. @@ -54,7 +54,7 @@ Calculates the number of vertices in the given `MISProblem`. OptimalBranchingCore.measure(p::MISProblem, ::NumOfVertices) = nv(p.g) """ - D3Measure +D3Measure A struct representing a measure that calculates the sum of the maximum degree minus 2 for each vertex in the graph. @@ -64,7 +64,7 @@ A struct representing a measure that calculates the sum of the maximum degree mi struct D3Measure <: AbstractMeasure end """ - measure(p::MISProblem, ::D3Measure) +measure(p::MISProblem, ::D3Measure) Calculates the D3 measure for the given `MISProblem`, which is defined as the sum of the maximum degree of each vertex minus 2, for all vertices in the graph. @@ -76,27 +76,24 @@ the maximum degree of each vertex minus 2, for all vertices in the graph. - `Int`: The computed D3 measure value. """ 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 + 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 function OptimalBranchingCore.size_reduction(p::MISProblem, m::D3Measure, cl::Clause{INT}, variables::Vector) where {INT} - vertices_removed = removed_vertices(variables, p.g, cl) - sum = 0 - for v in vertices_removed - sum += max(degree(p.g, v) - 2, 0) - end - vertices_removed_neighbors = setdiff(mapreduce(v -> neighbors(p.g, v), ∪, vertices_removed), vertices_removed) - for v in vertices_removed_neighbors - sum += max(degree(p.g, v) - 2) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed, neighbors(p.g, v)), 0) - end - # @show sum - # old_sum = measure(p, m) - measure(first(OptimalBranchingCore.apply_branch(p, cl, variables)), m) - # old_sum != sum && @show old_sum - return sum + vertices_removed = removed_vertices(variables, p.g, cl) + sum = 0 + for v in vertices_removed + sum += max(degree(p.g, v) - 2, 0) + end + vertices_removed_neighbors = setdiff(mapreduce(v -> neighbors(p.g, v), ∪, vertices_removed), vertices_removed) + for v in vertices_removed_neighbors + sum += max(degree(p.g, v) - 2) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed, neighbors(p.g, v)), 0) + end + return sum end diff --git a/lib/OptimalBranchingMIS/test/greedymerge.jl b/lib/OptimalBranchingMIS/test/greedymerge.jl index ea211c4..9973903 100644 --- a/lib/OptimalBranchingMIS/test/greedymerge.jl +++ b/lib/OptimalBranchingMIS/test/greedymerge.jl @@ -8,36 +8,35 @@ using GenericTensorNetworks using OptimalBranchingCore: bit_clauses Random.seed!(1234) +# Example from arXiv:2412.07685 Fig. 1 @testset "GreedyMerge" begin - edges = [(1, 4), (1, 5), (3, 4), (2, 5), (4, 5), (1, 6), (2, 7), (3, 8)] - example_g = SimpleGraph(Graphs.SimpleEdge.(edges)) - p = MISProblem(example_g) - tbl = BranchingTable(5, [ - [StaticElementVector(2, [0, 0, 0, 0, 1]), StaticElementVector(2, [0, 0, 0, 1, 0])], - [StaticElementVector(2, [0, 0, 1, 0, 1])], - [StaticElementVector(2, [0, 1, 0, 1, 0])], - [StaticElementVector(2, [1, 1, 1, 0, 0])], - ]) - cls = bit_clauses(tbl) - clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) - @test clsf[1].mask == cls[3][1].mask - @test clsf[1].val == cls[3][1].val - @test clsf[2].mask == cls[4][1].mask - @test clsf[2].val == cls[4][1].val - @test clsf[3].mask == 27 - @test clsf[3].val == 16 + edges = [(1, 4), (1, 5), (3, 4), (2, 5), (4, 5), (1, 6), (2, 7), (3, 8)] + example_g = SimpleGraph(Graphs.SimpleEdge.(edges)) + p = MISProblem(example_g) + tbl = BranchingTable(5, [ + [StaticElementVector(2, [0, 0, 0, 0, 1]), StaticElementVector(2, [0, 0, 0, 1, 0])], + [StaticElementVector(2, [0, 0, 1, 0, 1])], + [StaticElementVector(2, [0, 1, 0, 1, 0])], + [StaticElementVector(2, [1, 1, 1, 0, 0])], + ]) + cls = bit_clauses(tbl) + clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + @test clsf[1].mask == cls[3][1].mask + @test clsf[1].val == cls[3][1].val + @test clsf[2].mask == cls[4][1].mask + @test clsf[2].val == cls[4][1].val + @test clsf[3].mask == 27 + @test clsf[3].val == 16 end -@testset "mis" begin - for _num in 60:10:100 - g = random_regular_graph(_num, 3) - reducer = NoReducer() - bs = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = D3Measure(), set_cover_solver = OptimalBranchingCore.GreedyMerge()) - mis1,count1 = mis_branch_count(g; bs, reducer) - mis2,count2 = mis_branch_count(g;reducer) - if mis1 != mis2 - println("g") +@testset "GreedyMerge" begin + g = random_regular_graph(20, 3) + mis_num, count2 = mis_branch_count(g) + for reducer in [NoReducer(), MISReducer()] + for measure in [D3Measure(), NumOfVertices()] + bs = BranchingStrategy(table_solver = TensorNetworkSolver(), selector = MinBoundaryHighDegreeSelector(2, 6, 0), measure = measure, set_cover_solver = OptimalBranchingCore.GreedyMerge()) + mis1, count1 = mis_branch_count(g; branching_strategy = bs, reducer) + @test mis1 == mis_num end - @show count1,count2 - end -end \ No newline at end of file + end +end diff --git a/lib/OptimalBranchingMIS/test/types.jl b/lib/OptimalBranchingMIS/test/types.jl index 5b97785..560fbd7 100644 --- a/lib/OptimalBranchingMIS/test/types.jl +++ b/lib/OptimalBranchingMIS/test/types.jl @@ -5,11 +5,13 @@ using Random using OptimalBranchingCore using OptimalBranchingCore.BitBasis using GenericTensorNetworks -using OptimalBranchingCore: size_reduction +using OptimalBranchingCore: size_reduction, apply_branch @testset "size_reduction" begin g = random_regular_graph(60, 3) vs = collect(1:20) cl = Clause(bit"1111111111", bit"1011010111") - size_reduction(MISProblem(g),D3Measure(),cl,vs) -end \ No newline at end of file + p = MISProblem(g) + m = D3Measure() + @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m) +end From f2e54d1e405a3b1fc6e87a7cc6e6627990c92981 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 19:08:34 +0800 Subject: [PATCH 06/12] add test on coverd_by --- lib/OptimalBranchingCore/test/setcovering.jl | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/OptimalBranchingCore/test/setcovering.jl b/lib/OptimalBranchingCore/test/setcovering.jl index 57d723a..3b00f43 100644 --- a/lib/OptimalBranchingCore/test/setcovering.jl +++ b/lib/OptimalBranchingCore/test/setcovering.jl @@ -66,3 +66,35 @@ end @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) @test result_ip.γ ≈ 1.0 end + +@testset "covered_by" begin + tbl = BranchingTable(9, [ + [[0,0,0,0,0,1,1,0,0], [0,0,0,0,0,0,1,1,0]], + [[0,0,0,0,1,1,1,0,0]], + [[0,0,1,1,0,0,0,0,1], [0,0,1,1,0,1,0,0,0], [0,0,1,1,0,0,0,1,0]], + [[0,0,1,1,1,0,0,0,1], [0,0,1,1,1,1,0,0,0]], + [[0,1,0,0,0,0,1,1,0]], + [[0,1,0,1,1,0,0,0,1]], + [[0,1,1,0,1,0,0,0,1]], + [[0,1,1,1,0,0,0,0,1], [0,1,1,1,0,0,0,1,0]], + [[0,1,1,1,1,0,0,0,1]], + [[1,0,0,0,0,0,1,1,0]], + [[1,0,0,1,1,0,0,0,1]], + [[1,0,1,0,1,0,0,0,1]], + [[1,0,1,1,0,0,0,0,1], [1,0,1,1,0,0,0,1,0]], + [[1,0,1,1,1,0,0,0,1]], + [[1,1,0,0,0,0,1,1,0]], + [[1,1,0,1,1,0,0,0,1]], + [[1,1,1,0,1,0,0,0,1]], + [[1,1,1,1,0,0,0,0,1], [1,1,1,1,0,0,0,1,0]], + [[1,1,1,1,1,0,0,0,1]] + ]) + clauses = OptimalBranchingCore.candidate_clauses(tbl) + Δρ = [count_ones(c.mask) for c in clauses] + result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(max_itr = 10, verbose = false)) + @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) + + cls = OptimalBranchingCore.bit_clauses(tbl) + clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + @test OptimalBranchingCore.covered_by(tbl, DNF(clsf)) +end From 7231cea9bf7556942b9eaa69ad137cb64bee575f Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 20:56:16 +0800 Subject: [PATCH 07/12] fix test --- lib/OptimalBranchingMIS/test/types.jl | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/OptimalBranchingMIS/test/types.jl b/lib/OptimalBranchingMIS/test/types.jl index 560fbd7..0f3eba2 100644 --- a/lib/OptimalBranchingMIS/test/types.jl +++ b/lib/OptimalBranchingMIS/test/types.jl @@ -15,3 +15,37 @@ using OptimalBranchingCore: size_reduction, apply_branch m = D3Measure() @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m) end + + +@testset "covered_by" begin + tbl = BranchingTable(9, [ + [[0,0,0,0,0,1,1,0,0], [0,0,0,0,0,0,1,1,0]], + [[0,0,0,0,1,1,1,0,0]], + [[0,0,1,1,0,0,0,0,1], [0,0,1,1,0,1,0,0,0], [0,0,1,1,0,0,0,1,0]], + [[0,0,1,1,1,0,0,0,1], [0,0,1,1,1,1,0,0,0]], + [[0,1,0,0,0,0,1,1,0]], + [[0,1,0,1,1,0,0,0,1]], + [[0,1,1,0,1,0,0,0,1]], + [[0,1,1,1,0,0,0,0,1], [0,1,1,1,0,0,0,1,0]], + [[0,1,1,1,1,0,0,0,1]], + [[1,0,0,0,0,0,1,1,0]], + [[1,0,0,1,1,0,0,0,1]], + [[1,0,1,0,1,0,0,0,1]], + [[1,0,1,1,0,0,0,0,1], [1,0,1,1,0,0,0,1,0]], + [[1,0,1,1,1,0,0,0,1]], + [[1,1,0,0,0,0,1,1,0]], + [[1,1,0,1,1,0,0,0,1]], + [[1,1,1,0,1,0,0,0,1]], + [[1,1,1,1,0,0,0,0,1], [1,1,1,1,0,0,0,1,0]], + [[1,1,1,1,1,0,0,0,1]] + ]) + clauses = OptimalBranchingCore.candidate_clauses(tbl) + Δρ = [count_ones(c.mask) for c in clauses] + result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(max_itr = 10, verbose = false)) + @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) + + p = MISProblem(random_regular_graph(20, 3)) + cls = OptimalBranchingCore.bit_clauses(tbl) + clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + @test OptimalBranchingCore.covered_by(tbl, DNF(clsf)) +end \ No newline at end of file From 31f9a7a6988f27b70bd7cb1eeca889326d6c4710 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Fri, 3 Jan 2025 21:03:12 +0800 Subject: [PATCH 08/12] fix test --- lib/OptimalBranchingCore/test/setcovering.jl | 32 -------------------- 1 file changed, 32 deletions(-) diff --git a/lib/OptimalBranchingCore/test/setcovering.jl b/lib/OptimalBranchingCore/test/setcovering.jl index 3b00f43..57d723a 100644 --- a/lib/OptimalBranchingCore/test/setcovering.jl +++ b/lib/OptimalBranchingCore/test/setcovering.jl @@ -66,35 +66,3 @@ end @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) @test result_ip.γ ≈ 1.0 end - -@testset "covered_by" begin - tbl = BranchingTable(9, [ - [[0,0,0,0,0,1,1,0,0], [0,0,0,0,0,0,1,1,0]], - [[0,0,0,0,1,1,1,0,0]], - [[0,0,1,1,0,0,0,0,1], [0,0,1,1,0,1,0,0,0], [0,0,1,1,0,0,0,1,0]], - [[0,0,1,1,1,0,0,0,1], [0,0,1,1,1,1,0,0,0]], - [[0,1,0,0,0,0,1,1,0]], - [[0,1,0,1,1,0,0,0,1]], - [[0,1,1,0,1,0,0,0,1]], - [[0,1,1,1,0,0,0,0,1], [0,1,1,1,0,0,0,1,0]], - [[0,1,1,1,1,0,0,0,1]], - [[1,0,0,0,0,0,1,1,0]], - [[1,0,0,1,1,0,0,0,1]], - [[1,0,1,0,1,0,0,0,1]], - [[1,0,1,1,0,0,0,0,1], [1,0,1,1,0,0,0,1,0]], - [[1,0,1,1,1,0,0,0,1]], - [[1,1,0,0,0,0,1,1,0]], - [[1,1,0,1,1,0,0,0,1]], - [[1,1,1,0,1,0,0,0,1]], - [[1,1,1,1,0,0,0,0,1], [1,1,1,1,0,0,0,1,0]], - [[1,1,1,1,1,0,0,0,1]] - ]) - clauses = OptimalBranchingCore.candidate_clauses(tbl) - Δρ = [count_ones(c.mask) for c in clauses] - result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(max_itr = 10, verbose = false)) - @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) - - cls = OptimalBranchingCore.bit_clauses(tbl) - clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) - @test OptimalBranchingCore.covered_by(tbl, DNF(clsf)) -end From 771ae1128b1d4daacd429e3c5b9bc7906899f274 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 5 Jan 2025 15:23:24 +0800 Subject: [PATCH 09/12] update --- examples/rule_discovery.jl | 30 ++++++++++++- lib/OptimalBranchingCore/src/greedymerge.jl | 46 +++++++++----------- lib/OptimalBranchingCore/src/setcovering.jl | 8 ++-- lib/OptimalBranchingCore/test/setcovering.jl | 3 -- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/examples/rule_discovery.jl b/examples/rule_discovery.jl index 49e423b..4f87eb3 100644 --- a/examples/rule_discovery.jl +++ b/examples/rule_discovery.jl @@ -82,4 +82,32 @@ branching_region = SimpleGraph(Graphs.SimpleEdge.(edges)) # Generate the tree-like N3 neighborhood of R graph = tree_like_N3_neighborhood(branching_region) -solve_opt_rule(branching_region, graph, vs) \ No newline at end of file +solve_opt_rule(branching_region, graph, vs) + + +# ## Generating rules for large scale problems +# For large scale problems, we can use the greedy merge rule to generate rules, which avoids generating all candidate clauses. +function solve_greedy_rule(branching_region, graph, vs) + ## Use default solver and measure + m = D3Measure() + table_solver = TensorNetworkSolver(; prune_by_env=true) + + ## Pruning irrelevant entries + ovs = OptimalBranchingMIS.open_vertices(graph, vs) + subg, vmap = induced_subgraph(graph, vs) + @info "solving the branching table..." + tbl = OptimalBranchingMIS.reduced_alpha_configs(table_solver, subg, Int[findfirst(==(v), vs) for v in ovs]) + @info "the length of the truth_table after pruning irrelevant entries: $(length(tbl.table))" + + @info "generating the optimal branching rule via greedy merge..." + # greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector, m::AbstractMeasure) where {INT} + candidates = OptimalBranchingCore.bit_clauses(tbl) + result = OptimalBranchingMIS.OptimalBranchingCore.greedymerge(candidates, MISProblem(graph), vs, m) + return result + @info "the greedily minimized gamma: $(result.γ)" + + @info "the branching rule on R:" + viz_dnf(result.optimal_rule, vs) +end + +result = solve_greedy_rule(branching_region, graph, vs) diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl index 7fd164a..358bac6 100644 --- a/lib/OptimalBranchingCore/src/greedymerge.jl +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -21,32 +21,26 @@ function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, i, j = popfirst!(merging_pairs) if i in active_cls && j in active_cls for ii in 1:length(cls[i]), jj in 1:length(cls[j]) - if bdistance(cls[i][ii], cls[j][jj]) == 1 - cl12 = gather2(n, cls[i][ii], cls[j][jj]) - if cl12.mask == 0 - continue - end - l12 = size_reduction(problem, m, cl12, variables) - if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-8 - push!(cls, [cl12]) - k = length(cls) - deleteat!(active_cls, findfirst(==(i), active_cls)) - deleteat!(active_cls, findfirst(==(j), active_cls)) - for ii in active_cls - push!(merging_pairs, (ii, k)) - end - push!(active_cls, k) - push!(size_reductions, l12) - γ = complexity_bv(size_reductions[active_cls]) - break - end - end + cl12 = gather2(n, cls[i][ii], cls[j][jj]) + if cl12.mask == 0 + continue + end + l12 = size_reduction(problem, m, cl12, variables) + if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-12 + push!(cls, [cl12]) + k = length(cls) + deleteat!(active_cls, findfirst(==(i), active_cls)) + deleteat!(active_cls, findfirst(==(j), active_cls)) + for ii in active_cls + push!(merging_pairs, (ii, k)) + end + push!(active_cls, k) + push!(size_reductions, l12) + γ = complexity_bv(size_reductions[active_cls]) + break + end end end end - return [cl[1] for cl in cls[active_cls]] -end - -function size_reduction(p::AbstractProblem, m::AbstractMeasure, cl::Clause{INT}, variables::Vector) where {INT} - return measure(p, m) - measure(first(apply_branch(p, cl, variables)), m) -end + return OptimalBranchingResult(DNF([cl[1] for cl in cls[active_cls]]), [size_reductions[i] for i in active_cls], γ) +end \ No newline at end of file diff --git a/lib/OptimalBranchingCore/src/setcovering.jl b/lib/OptimalBranchingCore/src/setcovering.jl index 1c0ec0c..5d1f582 100644 --- a/lib/OptimalBranchingCore/src/setcovering.jl +++ b/lib/OptimalBranchingCore/src/setcovering.jl @@ -101,18 +101,16 @@ end The result type for the optimal branching rule. ### Fields -- `selected_ids::Vector{Int}`: The indices of the selected rows in the branching table. - `optimal_rule::DNF{INT}`: The optimal branching rule. - `branching_vector::Vector{T<:Real}`: The branching vector that records the size reduction in each subproblem. - `γ::Float64`: The optimal γ value (the complexity of the branching rule). """ struct OptimalBranchingResult{INT <: Integer, T <: Real} - selected_ids::Vector{Int} optimal_rule::DNF{INT} branching_vector::Vector{T} γ::Float64 end -Base.show(io::IO, results::OptimalBranchingResult{INT, T}) where {INT, T} = print(io, "OptimalBranchingResult{$INT, $T}:\n selected_ids: $(results.selected_ids)\n optimal_rule: $(results.optimal_rule)\n branching_vector: $(results.branching_vector)\n γ: $(results.γ)") +Base.show(io::IO, results::OptimalBranchingResult{INT, T}) where {INT, T} = print(io, "OptimalBranchingResult{$INT, $T}:\n optimal_rule: $(results.optimal_rule)\n branching_vector: $(results.branching_vector)\n γ: $(results.γ)") get_clauses(results::OptimalBranchingResult) = results.optimal_rule.clauses get_clauses(res::AbstractArray) = res @@ -142,7 +140,7 @@ function minimize_γ(table::BranchingTable, candidates::Vector{Clause{INT}}, Δ # Note: the following instance is captured for time saving, and also for it may cause IP solver to fail for (k, subset) in enumerate(subsets) - (length(subset) == num_items) && return OptimalBranchingResult([k], DNF([candidates[k]]), [Δρ[k]], 1.0) + (length(subset) == num_items) && return OptimalBranchingResult(DNF([candidates[k]]), [Δρ[k]], 1.0) end cx_old = cx = γ0 @@ -155,7 +153,7 @@ function minimize_γ(table::BranchingTable, candidates::Vector{Clause{INT}}, Δ cx ≈ cx_old && break # convergence cx_old = cx end - return OptimalBranchingResult(picked_scs, DNF([candidates[i] for i in picked_scs]), Δρ[picked_scs], cx) + return OptimalBranchingResult(DNF([candidates[i] for i in picked_scs]), Δρ[picked_scs], cx) end # TODO: we need to extend this function to trim the candidate clauses diff --git a/lib/OptimalBranchingCore/test/setcovering.jl b/lib/OptimalBranchingCore/test/setcovering.jl index 57d723a..3209b47 100644 --- a/lib/OptimalBranchingCore/test/setcovering.jl +++ b/lib/OptimalBranchingCore/test/setcovering.jl @@ -16,7 +16,6 @@ end Δρ = [count_ones(c.mask) for c in clauses] result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(; max_itr = 10, verbose = false)) result_lp = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, LPSolver(; max_itr = 10, verbose = false)) - @test result_ip.selected_ids == result_lp.selected_ids @test result_ip.branching_vector ≈ result_lp.branching_vector @test result_ip.γ ≈ result_lp.γ ≈ 1.0 @@ -29,7 +28,6 @@ end Δρ = [count_ones(c.mask) for c in clauses] result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(; max_itr = 10, verbose = false)) result_lp = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, LPSolver(; max_itr = 10, verbose = false)) - @test result_ip.selected_ids == result_lp.selected_ids @test result_ip.branching_vector ≈ result_lp.branching_vector @test result_ip.γ ≈ result_lp.γ ≈ 1.1673039782614185 @@ -47,7 +45,6 @@ end Δρ = [count_ones(c.mask) for c in clauses] result_ip = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, IPSolver(max_itr = 10, verbose = false)) result_lp = OptimalBranchingCore.minimize_γ(tbl, clauses, Δρ, LPSolver(max_itr = 10, verbose = false)) - @test result_ip.selected_ids == result_lp.selected_ids @test result_ip.branching_vector ≈ result_lp.branching_vector @test OptimalBranchingCore.covered_by(tbl, result_ip.optimal_rule) @test OptimalBranchingCore.covered_by(tbl, result_lp.optimal_rule) From a0fb2fd3f891088d150f575869e9c04f1ee17d1d Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 5 Jan 2025 16:23:26 +0800 Subject: [PATCH 10/12] update-greedy-implementation --- lib/OptimalBranchingCore/src/greedymerge.jl | 56 +++++++++------------ lib/OptimalBranchingMIS/src/types.jl | 1 + 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl index 358bac6..4f69374 100644 --- a/lib/OptimalBranchingCore/src/greedymerge.jl +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -11,36 +11,30 @@ function bit_clauses(tbl::BranchingTable{INT}) where {INT} end function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector, m::AbstractMeasure) where {INT} - active_cls = collect(1:length(cls)) - cls = copy(cls) - merging_pairs = [(i, j) for i in active_cls, j in active_cls if i < j] - n = length(variables) - size_reductions = [size_reduction(problem, m, candidate[1], variables) for candidate in cls] - γ = complexity_bv(size_reductions) - while !isempty(merging_pairs) - i, j = popfirst!(merging_pairs) - if i in active_cls && j in active_cls - for ii in 1:length(cls[i]), jj in 1:length(cls[j]) - cl12 = gather2(n, cls[i][ii], cls[j][jj]) - if cl12.mask == 0 - continue + cls = copy(cls) + size_reductions = [size_reduction(problem, m, first(candidate), variables) for candidate in cls] + local γ + while true + γ = complexity_bv(size_reductions) + minval = zero(γ) + minidx = (-1, -1, -1, -1) + local minclause + local minred + for i = 1:length(cls), j = i+1:length(cls) + for ii in 1:length(cls[i]), jj in 1:length(cls[j]) + cl12 = gather2(length(variables), cls[i][ii], cls[j][jj]) + reduction = size_reduction(problem, m, cl12, variables) + val = γ^(-reduction) - γ^(-size_reductions[i]) - γ^(-size_reductions[j]) + if val < minval + minval, minidx, minclause, minred = val, (i, j, ii, jj), cl12, reduction end - l12 = size_reduction(problem, m, cl12, variables) - if γ^(-size_reductions[i]) + γ^(-size_reductions[j]) >= γ^(-l12) + 1e-12 - push!(cls, [cl12]) - k = length(cls) - deleteat!(active_cls, findfirst(==(i), active_cls)) - deleteat!(active_cls, findfirst(==(j), active_cls)) - for ii in active_cls - push!(merging_pairs, (ii, k)) - end - push!(active_cls, k) - push!(size_reductions, l12) - γ = complexity_bv(size_reductions[active_cls]) - break - end - end - end - end - return OptimalBranchingResult(DNF([cl[1] for cl in cls[active_cls]]), [size_reductions[i] for i in active_cls], γ) + end + end + minidx == (-1, -1, -1, -1) && break # no more merging + deleteat!(cls, minidx[1:2]) + deleteat!(size_reductions, minidx[1:2]) + push!(cls, [minclause]) + push!(size_reductions, minred) + end + return OptimalBranchingResult(DNF([cl[1] for cl in cls]), size_reductions, γ) end \ No newline at end of file diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl index 4f64776..854b0ea 100644 --- a/lib/OptimalBranchingMIS/src/types.jl +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -87,6 +87,7 @@ end function OptimalBranchingCore.size_reduction(p::MISProblem, m::D3Measure, cl::Clause{INT}, variables::Vector) where {INT} vertices_removed = removed_vertices(variables, p.g, cl) + isempty(vertices_removed) && return 0 sum = 0 for v in vertices_removed sum += max(degree(p.g, v) - 2, 0) From 63ae46738cd1c855841f81f740e4cbf8ee951547 Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Mon, 6 Jan 2025 18:01:16 +0800 Subject: [PATCH 11/12] merge and fix bugs --- lib/OptimalBranchingCore/src/greedymerge.jl | 17 ++++++++++------- lib/OptimalBranchingMIS/src/types.jl | 16 ++++++++-------- lib/OptimalBranchingMIS/test/greedymerge.jl | 3 ++- lib/OptimalBranchingMIS/test/types.jl | 12 ++++++++++-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/OptimalBranchingCore/src/greedymerge.jl b/lib/OptimalBranchingCore/src/greedymerge.jl index 4f69374..26df3dc 100644 --- a/lib/OptimalBranchingCore/src/greedymerge.jl +++ b/lib/OptimalBranchingCore/src/greedymerge.jl @@ -1,18 +1,18 @@ struct GreedyMerge <: AbstractSetCoverSolver end function optimal_branching_rule(table::BranchingTable, variables::Vector, problem::AbstractProblem, m::AbstractMeasure, solver::GreedyMerge) - candidates = bit_clauses(table) - return greedymerge(candidates, problem, variables, m) + candidates = bit_clauses(table) + return greedymerge(candidates, problem, variables, m) end function bit_clauses(tbl::BranchingTable{INT}) where {INT} - n, bss = tbl.bit_length, tbl.table - temp_clauses = [[Clause(bmask(INT, 1:n), bs) for bs in bss1] for bss1 in bss] - return temp_clauses + n, bss = tbl.bit_length, tbl.table + temp_clauses = [[Clause(bmask(INT, 1:n), bs) for bs in bss1] for bss1 in bss] + return temp_clauses end function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector, m::AbstractMeasure) where {INT} cls = copy(cls) - size_reductions = [size_reduction(problem, m, first(candidate), variables) for candidate in cls] + size_reductions = [size_reduction(problem, m, first(candidate), variables) for candidate in cls] local γ while true γ = complexity_bv(size_reductions) @@ -20,9 +20,12 @@ function greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, minidx = (-1, -1, -1, -1) local minclause local minred - for i = 1:length(cls), j = i+1:length(cls) + for i ∈ 1:length(cls), j ∈ i+1:length(cls) for ii in 1:length(cls[i]), jj in 1:length(cls[j]) cl12 = gather2(length(variables), cls[i][ii], cls[j][jj]) + if cl12.mask == 0 + continue + end reduction = size_reduction(problem, m, cl12, variables) val = γ^(-reduction) - γ^(-size_reductions[i]) - γ^(-size_reductions[j]) if val < minval diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl index 854b0ea..28b9594 100644 --- a/lib/OptimalBranchingMIS/src/types.jl +++ b/lib/OptimalBranchingMIS/src/types.jl @@ -1,5 +1,5 @@ """ -mutable struct MISProblem <: AbstractProblem + mutable struct MISProblem <: AbstractProblem Represents a Maximum Independent Set (MIS) problem. @@ -19,8 +19,8 @@ Base.show(io::IO, p::MISProblem) = print(io, "MISProblem($(nv(p.g)))") Base.isempty(p::MISProblem) = nv(p.g) == 0 """ -TensorNetworkSolver -TensorNetworkSolver(; prune_by_env::Bool = true) + TensorNetworkSolver + TensorNetworkSolver(; prune_by_env::Bool = true) A struct representing a solver for tensor network problems. This struct serves as a specific implementation of the `AbstractTableSolver` type. @@ -30,7 +30,7 @@ This struct serves as a specific implementation of the `AbstractTableSolver` typ end """ -NumOfVertices + NumOfVertices A struct representing a measure that counts the number of vertices in a graph. Each vertex is counted as 1. @@ -41,7 +41,7 @@ Each vertex is counted as 1. struct NumOfVertices <: AbstractMeasure end """ -measure(p::MISProblem, ::NumOfVertices) + measure(p::MISProblem, ::NumOfVertices) Calculates the number of vertices in the given `MISProblem`. @@ -54,7 +54,7 @@ Calculates the number of vertices in the given `MISProblem`. OptimalBranchingCore.measure(p::MISProblem, ::NumOfVertices) = nv(p.g) """ -D3Measure + D3Measure A struct representing a measure that calculates the sum of the maximum degree minus 2 for each vertex in the graph. @@ -64,7 +64,7 @@ A struct representing a measure that calculates the sum of the maximum degree mi struct D3Measure <: AbstractMeasure end """ -measure(p::MISProblem, ::D3Measure) + measure(p::MISProblem, ::D3Measure) Calculates the D3 measure for the given `MISProblem`, which is defined as the sum of the maximum degree of each vertex minus 2, for all vertices in the graph. @@ -94,7 +94,7 @@ function OptimalBranchingCore.size_reduction(p::MISProblem, m::D3Measure, cl::Cl end vertices_removed_neighbors = setdiff(mapreduce(v -> neighbors(p.g, v), ∪, vertices_removed), vertices_removed) for v in vertices_removed_neighbors - sum += max(degree(p.g, v) - 2) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed, neighbors(p.g, v)), 0) + sum += max(degree(p.g, v) - 2,0) - max(degree(p.g, v) - 2 - count(vx -> vx ∈ vertices_removed, neighbors(p.g, v)), 0) end return sum end diff --git a/lib/OptimalBranchingMIS/test/greedymerge.jl b/lib/OptimalBranchingMIS/test/greedymerge.jl index 9973903..1799841 100644 --- a/lib/OptimalBranchingMIS/test/greedymerge.jl +++ b/lib/OptimalBranchingMIS/test/greedymerge.jl @@ -20,7 +20,8 @@ Random.seed!(1234) [StaticElementVector(2, [1, 1, 1, 0, 0])], ]) cls = bit_clauses(tbl) - clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + res = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], NumOfVertices()) + clsf = res.optimal_rule.clauses @test clsf[1].mask == cls[3][1].mask @test clsf[1].val == cls[3][1].val @test clsf[2].mask == cls[4][1].mask diff --git a/lib/OptimalBranchingMIS/test/types.jl b/lib/OptimalBranchingMIS/test/types.jl index 0f3eba2..f683507 100644 --- a/lib/OptimalBranchingMIS/test/types.jl +++ b/lib/OptimalBranchingMIS/test/types.jl @@ -14,6 +14,14 @@ using OptimalBranchingCore: size_reduction, apply_branch p = MISProblem(g) m = D3Measure() @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m) + + edges = [(1, 4), (1, 5), (3, 4), (2, 5), (4, 5), (1, 6), (2, 7), (3, 8)] + example_g = SimpleGraph(Graphs.SimpleEdge.(edges)) + p = MISProblem(example_g) + cl = Clause(bit"11111", bit"10000") + vs = collect(1:5) + m = D3Measure() + @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m) end @@ -46,6 +54,6 @@ end p = MISProblem(random_regular_graph(20, 3)) cls = OptimalBranchingCore.bit_clauses(tbl) - clsf = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) - @test OptimalBranchingCore.covered_by(tbl, DNF(clsf)) + res = OptimalBranchingCore.greedymerge(cls, p, [1, 2, 3, 4, 5], D3Measure()) + @test OptimalBranchingCore.covered_by(tbl, res.optimal_rule) end \ No newline at end of file From 88ae7927279ba2b257dee18e5f27824c7eec8b2e Mon Sep 17 00:00:00 2001 From: Zhongyi Date: Mon, 6 Jan 2025 19:00:30 +0800 Subject: [PATCH 12/12] fix doc --- examples/rule_discovery.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/rule_discovery.jl b/examples/rule_discovery.jl index 4f87eb3..732622d 100644 --- a/examples/rule_discovery.jl +++ b/examples/rule_discovery.jl @@ -100,7 +100,6 @@ function solve_greedy_rule(branching_region, graph, vs) @info "the length of the truth_table after pruning irrelevant entries: $(length(tbl.table))" @info "generating the optimal branching rule via greedy merge..." - # greedymerge(cls::Vector{Vector{Clause{INT}}}, problem::AbstractProblem, variables::Vector, m::AbstractMeasure) where {INT} candidates = OptimalBranchingCore.bit_clauses(tbl) result = OptimalBranchingMIS.OptimalBranchingCore.greedymerge(candidates, MISProblem(graph), vs, m) return result