Repository: OptimalBranching/OptimalBranching.jl
Pull request #52: new reducer: `TensorNetworkReducer`
Author: login: xuanzhaogao
Created: 2025-03-07T14:13:46Z | Merged: 2025-03-08T01:12:26Z

Major change: 
A new reducer called `TensorNetworkReducor` is added (proposed by @YijiawWang). 
In each step, it enumerates all vertices of the graph, selects a region including the vertex, gets the branching table and tries to find an non-zero intersection of the clauses, until all vertices are considered and no such intersetion is found.

Here are some results, the `kernelize` function is simply 
```julia
julia> function kernelize(g::SimpleGraph, reducer::AbstractReducer)
           p_old = MISProblem(g)
           while true
               rp, rv = reduce_problem(MaxSize, p_old, reducer)
               (p_old == rp) && return rp.g
               p_old = rp
           end
       end

julia> ksg = SimpleGraph(GenericTensorNetworks.random_diagonal_coupled_graph(40, 40, 0.8))
{1280, 3928} undirected simple Int64 graph

julia> kernelize(ksg, OptimalBranchingMIS.MISReducer())
{1235, 3832} undirected simple Int64 graph

julia> kernelize(ksg, OptimalBranchingMIS.XiaoReducer())
{222, 668} undirected simple Int64 graph

julia> kernelize(ksg, OptimalBranchingMIS.TensorNetworkReducer())
{128, 376} undirected simple Int64 graph
```

Minor changes:
1. added a function to calculate `size_reduction` for `NumOfVertices` measure.
2. added a set of function to find the intersection.
3. added deps `TropicalGEMM` for `OptimalBranchingMIS` (should I?)


Diff:
diff --git a/lib/OptimalBranchingCore/src/setcovering.jl b/lib/OptimalBranchingCore/src/setcovering.jl
index bfade16..0d5eab5 100644
--- a/lib/OptimalBranchingCore/src/setcovering.jl
+++ b/lib/OptimalBranchingCore/src/setcovering.jl
@@ -157,6 +157,55 @@ function minimize_γ(table::BranchingTable, candidates::Vector{Clause{INT}}, Δ
     return OptimalBranchingResult(DNF([candidates[i] for i in picked_scs]), Δρ[picked_scs], cx)
 end
 
+# try to find a non-zero intersection of clauses in the branching table
+# strategy: :dfs (depth-first search) or :bfs (breadth-first search), dfs gives the first found non-zero intersection, which covers the whole branching table, bfs gives the optimal result
+function intersect_clauses(tbl::BranchingTable{INT}, strategy::Symbol) where {INT}
+    n, bss = tbl.bit_length, tbl.table
+    tbl_clauses = [[Clause(bmask(INT, 1:n), bs[i]) for i in 1:length(bs)] for bs in bss]
+    length(tbl_clauses) == 1 && return tbl_clauses[1]
+    sort!(tbl_clauses, by = x -> length(x))
+
+    if strategy == :dfs
+        for c0 in tbl_clauses[1]
+            c_res = _intersect_clauses_dfs((@view tbl_clauses[2:end]), c0, n)
+            c_res.mask != 0 && return [c_res]
+        end
+
+        # not found any non-zero intersection
+        return Clause{INT}[]
+    elseif strategy == :bfs
+        c0s = _intersect_clauses_bfs((@view tbl_clauses[2:end]), tbl_clauses[1], n)
+        return c0s
+    else 
+        error("Invalid strategy: $strategy, must be :dfs or :bfs")
+    end
+end
+
+function _intersect_clauses_dfs(tbl_clauses::AbstractVector{Vector{Clause{INT}}}, c0::Clause{INT}, n::Int) where {INT}
+    flag = (length(tbl_clauses) == 1) # flag is true if reaching the last layer
+    for ci in tbl_clauses[1]
+        c_new = gather2(n, c0, ci)
+        if c_new.mask != 0 # skip the zero clause in middle layers
+            flag && (return c_new) # return the final result if reaching the last layer
+            c_res = _intersect_clauses_dfs((@view tbl_clauses[2:end]), c_new, n) # not the last layer, continue to intersect
+            (c_res.mask != 0) && return c_res # return the result if the intersection is non-zero, stops the recursion
+        end
+    end
+    return Clause(bmask(INT, 0), bmask(INT, 0))
+end
+
+function _intersect_clauses_bfs(tbl_clauses::AbstractVector{Vector{Clause{INT}}}, cs::Vector{Clause{INT}}, n::Int) where {INT}
+    new_cs = Vector{Clause{INT}}()
+    for ci in tbl_clauses[1]
+        for cj in cs
+            c_new = gather2(n, cj, ci)
+            (c_new.mask != 0) && push!(new_cs, c_new)
+        end
+    end
+    unique!(new_cs)
+    return (length(tbl_clauses) == 1 || isempty(new_cs)) ? new_cs : _intersect_clauses_bfs((@view tbl_clauses[2:end]), new_cs, n)
+end
+
 # TODO: we need to extend this function to trim the candidate clauses
 """
     candidate_clauses(tbl::BranchingTable{INT}) where {INT}
diff --git a/lib/OptimalBranchingCore/test/setcovering.jl b/lib/OptimalBranchingCore/test/setcovering.jl
index 3209b47..12777ce 100644
--- a/lib/OptimalBranchingCore/test/setcovering.jl
+++ b/lib/OptimalBranchingCore/test/setcovering.jl
@@ -6,6 +6,28 @@ using Test
     @test OptimalBranchingCore.bisect_solve(f, 1.0, f(1.0), 2.0, f(2.0)) ≈ sqrt(2)
 end
 
+@testset "intersection of clauses" begin
+    tbl = BranchingTable(5, [
+        [[0, 1, 0, 1, 0], [0, 1, 1, 0, 0]],
+        [[1, 1, 0, 1, 0]],
+        [[0, 0, 1, 0, 1]]
+    ])
+    ct_dfs = OptimalBranchingCore.intersect_clauses(tbl, :dfs)
+    ct_bfs = OptimalBranchingCore.intersect_clauses(tbl, :bfs)
+    @test isempty(ct_dfs)
+    @test isempty(ct_bfs)
+
+    tbl = BranchingTable(5, [
+        [[0, 1, 0, 1, 0], [0, 1, 1, 0, 0], [0, 0, 1, 0, 0]],
+        [[1, 1, 0, 1, 0]],
+        [[0, 0, 1, 0, 1], [0, 1, 1, 0, 1]]
+    ])
+    ct_dfs = OptimalBranchingCore.intersect_clauses(tbl, :dfs)
+    ct_bfs = OptimalBranchingCore.intersect_clauses(tbl, :bfs)
+    @test count_ones(ct_dfs[1].mask) == 1
+    @test count_ones(ct_bfs[1].mask) == 1
+end
+
 @testset "setcover by JuMP - StaticBitVector type" begin
     tbl = BranchingTable(5, [
         [StaticBitVector([0, 0, 1, 0, 0]), StaticBitVector([0, 1, 0, 0, 0])],
diff --git a/lib/OptimalBranchingMIS/Project.toml b/lib/OptimalBranchingMIS/Project.toml
index 1c4ae8c..0f77b23 100644
--- a/lib/OptimalBranchingMIS/Project.toml
+++ b/lib/OptimalBranchingMIS/Project.toml
@@ -18,11 +18,13 @@ GenericTensorNetworks = "3"
 KaHyPar = "0.3.1"
 OptimalBranchingCore = "0.1.0"
 SparseArrays = "1.11.0"
+TropicalGEMM = "0.2.0"
 julia = "1.10"
 
 [extras]
 Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+TropicalGEMM = "a4ad3063-64a7-4bad-8738-34ed09bc0236"
 
 [targets]
-test = ["Random", "Test"]
+test = ["Random", "Test", "TropicalGEMM"]
diff --git a/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl b/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl
index cc35045..af22c62 100644
--- a/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl
+++ b/lib/OptimalBranchingMIS/src/OptimalBranchingMIS.jl
@@ -1,7 +1,7 @@
 module OptimalBranchingMIS
 
 using OptimalBranchingCore
-using OptimalBranchingCore: AbstractProblem
+using OptimalBranchingCore: AbstractProblem, intersect_clauses, candidate_clauses
 using OptimalBranchingCore.BitBasis
 using Combinatorics
 using EliminateGraphs, EliminateGraphs.Graphs
@@ -10,7 +10,7 @@ using SparseArrays
 using KaHyPar
 
 export MISProblem
-export MISReducer, XiaoReducer
+export MISReducer, XiaoReducer, TensorNetworkReducer
 export MinBoundarySelector, MinBoundaryHighDegreeSelector,KaHyParSelector
 export TensorNetworkSolver
 export NumOfVertices, D3Measure
diff --git a/lib/OptimalBranchingMIS/src/cut_kKaHyPar_sea20.ini b/lib/OptimalBranchingMIS/src/ini/cut_kKaHyPar_sea20.ini
similarity index 100%
rename from lib/OptimalBranchingMIS/src/cut_kKaHyPar_sea20.ini
rename to lib/OptimalBranchingMIS/src/ini/cut_kKaHyPar_sea20.ini
diff --git a/lib/OptimalBranchingMIS/src/reducer.jl b/lib/OptimalBranchingMIS/src/reducer.jl
index c4ee135..ac43286 100644
--- a/lib/OptimalBranchingMIS/src/reducer.jl
+++ b/lib/OptimalBranchingMIS/src/reducer.jl
@@ -95,4 +95,75 @@ function OptimalBranchingCore.reduce_problem(::Type{R}, p::MISProblem, ::XiaoRed
     end
 
     return p, R(0)
+end
+
+"""
+    struct TensorNetworkReducer <: AbstractReducer
+
+A reducer that uses tensor network contraction to find reduction rules.
+
+# Fields
+- `n_max::Int = 15`: Maximum number of vertices to be included in the selected region
+- `selector::Symbol = :mincut`: Strategy for selecting vertices to contract. Options are:
+    - `:neighbor`: Select vertices based on neighborhood
+    - `:mincut`: Select vertices based on minimum cut provided by KaHyPar
+- `measure::AbstractMeasure = NumOfVertices()`: Measure used for kernelization. Uses size reduction from OptimalBranchingMIS
+- `intersect_strategy::Symbol = :bfs`: Strategy for intersecting clauses. Options are:
+    - `:bfs`: Breadth-first search (gives the optimal result)
+    - `:dfs`: Depth-first search (gives the first found non-zero intersection)
+- `sub_reducer::AbstractReducer = XiaoReducer()`: Reducer applied to selected vertices before tensor network contraction, default is XiaoReducer
+"""
+@kwdef struct TensorNetworkReducer <: AbstractReducer
+    n_max::Int = 15
+    selector::Symbol = :mincut # :neighbor or :mincut
+    measure::AbstractMeasure = NumOfVertices() # different measures for kernelization, use the size reduction from OptimalBranchingMIS
+    intersect_strategy::Symbol = :bfs # :dfs or :bfs
+    sub_reducer::AbstractReducer = XiaoReducer() # sub reducer for the selected vertices
+end
+Base.show(io::IO, reducer::TensorNetworkReducer) = print(io,
+    """
+    TensorNetworkReducer
+        ├── n_max - $(reducer.n_max)
+        ├── selector - $(reducer.selector)
+        ├── measure - $(reducer.measure)
+        ├── intersect_strategy - $(reducer.intersect_strategy)
+        └── sub_reducer - $(reducer.sub_reducer)
+    """)
+
+function OptimalBranchingCore.reduce_problem(result_type::Type{R}, p::MISProblem, tnreducer::TensorNetworkReducer) where R
+    rp, reducedvalue = reduce_problem(result_type, p, tnreducer.sub_reducer)
+    (rp.g != p.g) && return rp, reducedvalue
+
+    for i in 1:nv(p.g)
+        selected_vertices = select_region(p.g, i, tnreducer.n_max, tnreducer.selector)
+        truth_table = branching_table(p, TensorNetworkSolver(), selected_vertices)
+        bc = best_intersect(p, truth_table, tnreducer.measure, tnreducer.intersect_strategy, 
+        selected_vertices)
+        if !isnothing(bc)
+            rp, reducedvalue = OptimalBranchingCore.apply_branch(p, bc, selected_vertices)
+            return rp, R(reducedvalue)
+        end
+    end
+
+    return p, R(0)
+end
+
+function best_intersect(p::MISProblem, tbl::BranchingTable, measure::AbstractMeasure, intersect_strategy::Symbol, variables::Vector{T}) where {T}
+    cl = OptimalBranchingCore.intersect_clauses(tbl, intersect_strategy)
+    if isempty(cl)
+        return nothing
+    elseif length(cl) == 1
+        return cl[1]
+    else
+        best_loss = OptimalBranchingCore.size_reduction(p, measure, cl[1], variables)
+        best_cl = cl[1]
+        for c in cl[2:end]
+            loss = OptimalBranchingCore.size_reduction(p, measure, c, variables)
+            if loss < best_loss
+                best_loss = loss
+                best_cl = c
+            end
+        end
+        return best_cl
+    end
 end
\ No newline at end of file
diff --git a/lib/OptimalBranchingMIS/src/selector.jl b/lib/OptimalBranchingMIS/src/selector.jl
index b0f611c..88aab71 100644
--- a/lib/OptimalBranchingMIS/src/selector.jl
+++ b/lib/OptimalBranchingMIS/src/selector.jl
@@ -1,5 +1,5 @@
 """
-    struct MinBoundarySelector <: AbstractVertexSelector
+    struct MinBoundarySelector <: AbstractSelector
 
 The `MinBoundarySelector` struct represents a strategy for selecting a subgraph with the minimum number of open vertices by k-layers of neighbors.
 
@@ -82,7 +82,7 @@ function OptimalBranchingCore.select_variables(p::MISProblem, m::M, selector::Ka
     h = KaHyPar.HyperGraph(edge2vertex(p))
     imbalance = 1-2*selector.app_domain_size/nv(p.g)
     
-    parts = KaHyPar.partition(h, 2; configuration = pkgdir(@__MODULE__, "src", "cut_kKaHyPar_sea20.ini"), imbalance)
+    parts = KaHyPar.partition(h, 2; configuration = pkgdir(@__MODULE__, "src/ini", "cut_kKaHyPar_sea20.ini"), imbalance)
 
     zero_num = count(x-> x ≈ 0,parts)
     one_num = length(parts)-zero_num
@@ -91,20 +91,46 @@ function OptimalBranchingCore.select_variables(p::MISProblem, m::M, selector::Ka
     return abs(zero_num-selector.app_domain_size) < abs(one_num-selector.app_domain_size) ? findall(iszero,parts) : findall(!iszero,parts)
 end
 
-function edge2vertex(p::MISProblem)
+edge2vertex(p::MISProblem) = edge2vertex(p.g)
+function edge2vertex(g::SimpleGraph)
     I = Int[]
     J = Int[]
     edgecount = 0
-    for i in 1:nv(p.g)-1
-        for j in p.g.fadjlist[i]
-            if j >i
-                edgecount += 1
-                push!(I,i)
-                push!(I,j)
-                push!(J, edgecount)
-                push!(J, edgecount)
-            end
+    @inbounds for i in 1:nv(g)-1, j in g.fadjlist[i]
+        if j >i
+            edgecount += 1
+            push!(I,i)
+            push!(I,j)
+            push!(J, edgecount)
+            push!(J, edgecount)
         end
     end
     return sparse(I, J, ones(length(I)))
-end
\ No newline at end of file
+end
+
+# region selectors, max size is n_max and a vertex i is required to be in the region
+function select_region(g::AbstractGraph, i::Int, n_max::Int, strategy::Symbol)
+    if strategy == :neighbor
+        vs = [i]
+        while length(vs) < n_max
+            nbrs = open_neighbors(g, vs)
+            (length(vs) + length(nbrs) > n_max) && break
+            append!(vs, nbrs)
+        end
+        return vs
+    elseif strategy == :mincut
+        nv(g) <= n_max && return collect(1:nv(g))
+
+        fix_vs = fill(-1, nv(g)) 
+        fix_vs[i] = 0  
+
+        h = KaHyPar.HyperGraph(edge2vertex(g))
+        KaHyPar.fix_vertices(h, nv(g)-1, fix_vs)
+        KaHyPar.set_target_block_weights(h, [n_max,nv(g) - n_max])
+        parts = KaHyPar.partition(h, 2; configuration = pkgdir(@__MODULE__, "src/ini", "cut_kKaHyPar_sea20.ini"))
+        
+        return findall(iszero,parts)
+    else
+        error("Invalid strategy: $strategy, must be :neighbor or :mincut")
+    end
+end
diff --git a/lib/OptimalBranchingMIS/src/types.jl b/lib/OptimalBranchingMIS/src/types.jl
index 2872fa0..2c90adf 100644
--- a/lib/OptimalBranchingMIS/src/types.jl
+++ b/lib/OptimalBranchingMIS/src/types.jl
@@ -55,6 +55,9 @@ Calculates the number of vertices in the given `MISProblem`.
 - `Int`: The number of vertices in the graph.
 """
 OptimalBranchingCore.measure(p::MISProblem, ::NumOfVertices) = nv(p.g)
+function OptimalBranchingCore.size_reduction(p::MISProblem{INT}, ::NumOfVertices, cl::Clause, variables::Vector) where {INT}
+    return count_ones(removed_mask(INT, variables, p.g, cl))
+end
 
 """
     D3Measure
diff --git a/lib/OptimalBranchingMIS/test/branch.jl b/lib/OptimalBranchingMIS/test/branch.jl
index 5376e35..9c6c713 100644
--- a/lib/OptimalBranchingMIS/test/branch.jl
+++ b/lib/OptimalBranchingMIS/test/branch.jl
@@ -3,6 +3,7 @@ using OptimalBranchingCore
 using Test, Random
 
 @testset "branch_and_reduce" begin
+    @info "branch_and_reduce"
     Random.seed!(1234)
     for n in [40]
         for d in [3,4]
@@ -11,7 +12,8 @@ using Test, Random
             mis_exact = mis2(EliminateGraph(g))
             p = MISProblem(g)
 
-            for set_cover_solver in [IPSolver(max_itr = 10, verbose = false), LPSolver(max_itr = 10, verbose = false)], measure in [D3Measure(), NumOfVertices()], reducer in [NoReducer(), MISReducer(), XiaoReducer()], prune_by_env in [true, false], selector in [MinBoundarySelector(2), MinBoundaryHighDegreeSelector(2, 6, 0), MinBoundaryHighDegreeSelector(2, 6, 1)]
+            for set_cover_solver in [IPSolver(max_itr = 10, verbose = false), LPSolver(max_itr = 10, verbose = false)], measure in [D3Measure(), NumOfVertices()], reducer in [NoReducer(), MISReducer(), XiaoReducer(), TensorNetworkReducer()], prune_by_env in [true, false], selector in [MinBoundarySelector(2), MinBoundaryHighDegreeSelector(2, 6, 0), MinBoundaryHighDegreeSelector(2, 6, 1)]
+                @info "set_cover_solver = $set_cover_solver, measure = $measure, reducer = $reducer, prune_by_env = $prune_by_env, selector = $selector"
                 branching_strategy = BranchingStrategy(; set_cover_solver, table_solver=TensorNetworkSolver(; prune_by_env), selector=selector, measure)
                 res = branch_and_reduce(p, branching_strategy, reducer, MaxSize)
                 res_count = branch_and_reduce(p, branching_strategy, reducer, MaxSizeBranchCount)
diff --git a/lib/OptimalBranchingMIS/test/runtests.jl b/lib/OptimalBranchingMIS/test/runtests.jl
index bf99d23..8fdcd98 100644
--- a/lib/OptimalBranchingMIS/test/runtests.jl
+++ b/lib/OptimalBranchingMIS/test/runtests.jl
@@ -1,4 +1,5 @@
 using OptimalBranchingMIS
+using TropicalGEMM
 using Test
 
 @testset "branch" begin
diff --git a/lib/OptimalBranchingMIS/test/selector.jl b/lib/OptimalBranchingMIS/test/selector.jl
index 2678df6..7d477ac 100644
--- a/lib/OptimalBranchingMIS/test/selector.jl
+++ b/lib/OptimalBranchingMIS/test/selector.jl
@@ -20,3 +20,14 @@ end
     miskn, countkn = mis_branch_count(g; branching_strategy = bs)
     @test mis1 == miskn
 end
+
+@testset "region selection" begin
+    g = random_regular_graph(100, 3)
+    for strategy in [:neighbor, :mincut]
+        for i in rand(1:100, 10)
+            selected_vertices = OptimalBranchingMIS.select_region(g, i, 15, strategy)
+            @test length(selected_vertices) ≤ 15
+            @test i ∈ selected_vertices
+        end
+    end
+end
\ No newline at end of file
diff --git a/lib/OptimalBranchingMIS/test/types.jl b/lib/OptimalBranchingMIS/test/types.jl
index 1e923c5..1e5550f 100644
--- a/lib/OptimalBranchingMIS/test/types.jl
+++ b/lib/OptimalBranchingMIS/test/types.jl
@@ -8,25 +8,27 @@ using GenericTensorNetworks
 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")
-    p = MISProblem(g)
-    m = D3Measure()
-    @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m)
+    for m in [NumOfVertices(), D3Measure()]
+        g = random_regular_graph(60, 3)
+        vs = collect(1:20)
+        cl = Clause(bit"1111111111", bit"1011010111")
+        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)
+        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)
 
-    g = random_regular_graph(200, 3; seed = 2134)
-    vs = [6, 26, 81, 111, 56, 47, 101, 153]
-    cl = Clause(bit"11001111", bit"11000001")
-    p = MISProblem(g)
-    m = D3Measure()
-    @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m)
+        g = random_regular_graph(200, 3; seed = 2134)
+        vs = [6, 26, 81, 111, 56, 47, 101, 153]
+        cl = Clause(bit"11001111", bit"11000001")
+        p = MISProblem(g)
+        m = D3Measure()
+        @test size_reduction(p, m, cl, vs) == measure(p, m) - measure(first(apply_branch(p, cl, vs)), m)
+    end
 end
\ No newline at end of file


Reviews:
user: login: GiggleLiu
body: Good job!
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T17:02:03Z
user: login: xuanzhaogao
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T17:32:15Z

Review comments:
diff_hunk: @@ -157,6 +157,55 @@ function minimize_γ(table::BranchingTable, candidates::Vector{Clause{INT}}, Δ
     return OptimalBranchingResult(DNF([candidates[i] for i in picked_scs]), Δρ[picked_scs], cx)
 end
 
+# try to find a non-zero intersection of clauses in the branching table
+# strategy: :dfs (depth-first search) or :bfs (breadth-first search), dfs gives the first found non-zero intersection, bfs gives the optimal result
+function intersect_clauses(tbl::BranchingTable{INT}, strategy::Symbol) where {INT}
path: lib/OptimalBranchingCore/src/setcovering.jl
user: login: GiggleLiu
body: 1. If it is exported, then it should be documented.
2. What is the nonzero intersection of clauses?
created_at: 2025-03-07T16:57:21Z
author_association: COLLABORATOR
diff_hunk: @@ -10,6 +10,7 @@ GenericTensorNetworks = "3521c873-ad32-4bb4-b63d-f4f178f42b49"
 KaHyPar = "2a6221f6-aa48-11e9-3542-2d9e0ef01880"
 OptimalBranchingCore = "c76e7b22-e1d2-40e8-b0f1-f659837787b8"
 SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+TropicalGEMM = "a4ad3063-64a7-4bad-8738-34ed09bc0236"
path: lib/OptimalBranchingMIS/Project.toml
user: login: GiggleLiu
body: Remove it from the dependency, this package is buggy in many machines.
created_at: 2025-03-07T16:57:51Z
author_association: COLLABORATOR
diff_hunk: @@ -91,20 +91,46 @@ function OptimalBranchingCore.select_variables(p::MISProblem, m::M, selector::Ka
     return abs(zero_num-selector.app_domain_size) < abs(one_num-selector.app_domain_size) ? findall(iszero,parts) : findall(!iszero,parts)
 end
 
-function edge2vertex(p::MISProblem)
+edge2vertex(p::MISProblem) = edge2vertex(p.g)
+function edge2vertex(g::SimpleGraph)
     I = Int[]
     J = Int[]
     edgecount = 0
-    for i in 1:nv(p.g)-1
-        for j in p.g.fadjlist[i]
-            if j >i
-                edgecount += 1
-                push!(I,i)
-                push!(I,j)
-                push!(J, edgecount)
-                push!(J, edgecount)
-            end
+    @inbounds for i in 1:nv(g)-1, j in g.fadjlist[i]
+        if j >i
+            edgecount += 1
+            push!(I,i)
+            push!(I,j)
+            push!(J, edgecount)
+            push!(J, edgecount)
         end
     end
     return sparse(I, J, ones(length(I)))
-end
\ No newline at end of file
+end
+
+# region selectors, max size is n_max and a vertex i is required to be in the region
+function select_region(g::AbstractGraph, i::Int, n_max::Int, strategy::Symbol)
+    if strategy == :neighbor
+        vs = [i]
+        while length(vs) < n_max
+            nbrs = open_neighbors(g, vs)
+            (length(vs) + length(nbrs) > n_max) && break
+            append!(vs, nbrs)
+        end
+        return vs
path: lib/OptimalBranchingMIS/src/selector.jl
user: login: GiggleLiu
body: Please get these lines tested.
created_at: 2025-03-07T17:01:16Z
author_association: COLLABORATOR
diff_hunk: @@ -157,6 +157,55 @@ function minimize_γ(table::BranchingTable, candidates::Vector{Clause{INT}}, Δ
     return OptimalBranchingResult(DNF([candidates[i] for i in picked_scs]), Δρ[picked_scs], cx)
 end
 
+# try to find a non-zero intersection of clauses in the branching table
+# strategy: :dfs (depth-first search) or :bfs (breadth-first search), dfs gives the first found non-zero intersection, bfs gives the optimal result
+function intersect_clauses(tbl::BranchingTable{INT}, strategy::Symbol) where {INT}
path: lib/OptimalBranchingCore/src/setcovering.jl
user: login: xuanzhaogao
body: 1. I think they should be low level api, no more exported.
2. non zeros intersection means that it covers the whole branching table
created_at: 2025-03-07T17:32:15Z
author_association: COLLABORATOR

PR comments:
user: login: GiggleLiu
created_at: 2025-03-07T14:17:41Z
body: Amazing performance!
author_association: COLLABORATOR
user: login: codecov[bot]
created_at: 2025-03-07T14:24:23Z
body: ## [Codecov](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
Attention: Patch coverage is `88.54167%` with `11 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Patch % | Lines |
|---|---|---|
| [lib/OptimalBranchingMIS/src/reducer.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingMIS%2Fsrc%2Freducer.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdNSVMvc3JjL3JlZHVjZXIuamw=) | 70.00% | [9 Missing :warning: ](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) |
| [lib/OptimalBranchingCore/src/setcovering.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingCore%2Fsrc%2Fsetcovering.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdDb3JlL3NyYy9zZXRjb3ZlcmluZy5qbA==) | 97.14% | [1 Missing :warning: ](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) |
| [lib/OptimalBranchingMIS/src/selector.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingMIS%2Fsrc%2Fselector.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdNSVMvc3JjL3NlbGVjdG9yLmps) | 96.55% | [1 Missing :warning: ](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) |

| [Files with missing lines](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [lib/OptimalBranchingMIS/src/types.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingMIS%2Fsrc%2Ftypes.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdNSVMvc3JjL3R5cGVzLmps) | `87.09% <100.00%> (-2.56%)` | :arrow_down: |
| [lib/OptimalBranchingCore/src/setcovering.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingCore%2Fsrc%2Fsetcovering.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdDb3JlL3NyYy9zZXRjb3ZlcmluZy5qbA==) | `95.80% <97.14%> (+0.35%)` | :arrow_up: |
| [lib/OptimalBranchingMIS/src/selector.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingMIS%2Fsrc%2Fselector.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdNSVMvc3JjL3NlbGVjdG9yLmps) | `96.10% <96.55%> (-0.51%)` | :arrow_down: |
| [lib/OptimalBranchingMIS/src/reducer.jl](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52?src=pr&el=tree&filepath=lib%2FOptimalBranchingMIS%2Fsrc%2Freducer.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bGliL09wdGltYWxCcmFuY2hpbmdNSVMvc3JjL3JlZHVjZXIuamw=) | `86.41% <70.00%> (-9.66%)` | :arrow_down: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/OptimalBranching/OptimalBranching.jl/pull/52/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: GiggleLiu
created_at: 2025-03-07T15:03:21Z
body: > added deps TropicalGEMM for OptimalBranchingMIS (should I?)

I do not think so, because TropicalGEMM depends on LoopVectorization, which is very heavy, and will not be be compatible with Julia 1.12. So, maybe an extension is more suitible.
author_association: COLLABORATOR
user: login: xuanzhaogao
created_at: 2025-03-07T17:47:00Z
body: tests, docs added
deps removed
author_association: COLLABORATOR