From 665e66f0c59009f5f228c8ef2bce7fdfffac15db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Thu, 25 Apr 2024 17:52:08 +0200 Subject: [PATCH 1/6] independent set --- docs/src/algorithms.md | 9 ++++++++ src/GraphsOptim.jl | 1 + src/independent_set.jl | 47 +++++++++++++++++++++++++++++++++++++++++ test/independent_set.jl | 22 +++++++++++++++++++ test/runtests.jl | 4 ++++ 5 files changed, 83 insertions(+) create mode 100644 src/independent_set.jl create mode 100644 test/independent_set.jl diff --git a/docs/src/algorithms.md b/docs/src/algorithms.md index ad088f9..aed8f28 100644 --- a/docs/src/algorithms.md +++ b/docs/src/algorithms.md @@ -70,6 +70,15 @@ GraphsOptim.min_vertex_cover! Finds a subset $S \subset V$ of vertices of an undirected graph $G = (V,E)$ such that $\forall (u,v) \in E: u \in S \lor v \in S$ +## Maximum Weight Independent Set + +```@docs +maximum_weight_independent_set +GraphsOptim.maximum_weight_independent_set! +``` + +Finds a subset $S \subset V$ of vertices of maximal weight of an undirected graph $G = (V,E)$ such that $\forall (u,v) \in E: u \notin S \lor v \notin S$. + ## Graph matching !!! danger "Work in progress" diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl index dc03c46..29e7e55 100644 --- a/src/GraphsOptim.jl +++ b/src/GraphsOptim.jl @@ -34,5 +34,6 @@ include("graph_matching.jl") include("min_vertex_cover.jl") include("fractional_coloring.jl") include("shortest_path.jl") +include("independent_set.jl") end diff --git a/src/independent_set.jl b/src/independent_set.jl new file mode 100644 index 0000000..12d0778 --- /dev/null +++ b/src/independent_set.jl @@ -0,0 +1,47 @@ + +""" + maximum_independent_set!(model, g; var_name) + +Computes in-place in the JuMP model a maximum-weighted independent set of `g`. +An optional `vertex_weights` vector can be passed to the graph, defaulting to uniform weights (computing a maximum size independent set). +""" +function maximum_weight_independent_set!( + model::Model, g::AbstractGraph; binary::Bool=true, var_name, vertex_weights=ones(nv(g)) +) + if is_directed(g) + throw(ArgumentError("The graph must not be directed")) + end + g_vertices = collect(vertices(g)) + f = @variable(model, [g_vertices]; binary=binary, base_name=String(var_name)) + model[Symbol(var_name)] = f + @constraint( + model, + covering_constraint[i=1:nv(g), j=1:nv(g); i ≠ j && has_edge(g, i, j)], + f[i] + f[j] <= 1, + ) + @objective(model, Max, dot(f, vertex_weights)) + return model +end + +""" + maximum_weight_independent_set(g; optimizer, binary, vertex_weights) + +Computes a maximum-weighted independent set or stable set of `g`. +""" +function maximum_weight_independent_set( + g::AbstractGraph; + binary::Bool=true, + vertex_weights=ones(nv(g)), + optimizer=HiGHS.Optimizer, +) + model = Model(optimizer) + set_silent(model) + maximum_weight_independent_set!( + model, g; binary=binary, vertex_weights=vertex_weights, var_name=:stable + ) + optimize!(model) + @assert termination_status(model) == OPTIMAL + stable_variables = Vector(model[:stable]) + stable_vertices = findall(v -> value(v) > 0.5, stable_variables) + return stable_vertices +end diff --git a/test/independent_set.jl b/test/independent_set.jl new file mode 100644 index 0000000..5db97f7 --- /dev/null +++ b/test/independent_set.jl @@ -0,0 +1,22 @@ +using GraphsOptim +using Graphs +using Test + +g = Graphs.random_regular_graph(10, 5) + +for _ in 1:10 + vertex_weights = rand(nv(g)) + stable = GraphsOptim.maximum_weight_independent_set(g; vertex_weights=vertex_weights) + if length(stable) > 1 + for idx in 1:(length(stable) - 1) + @test !Graphs.has_edge(g, stable[idx], stable[idx + 1]) + end + end +end + +g2 = complete_graph(3) +add_vertex!(g2) +add_edge!(g2, 3, 4) +stable = GraphsOptim.maximum_weight_independent_set(g2) +@test length(stable) == 2 +@test 4 in stable diff --git a/test/runtests.jl b/test/runtests.jl index 8811777..4773df1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -44,6 +44,10 @@ using Test include("min_vertex_cover.jl") end + @testset verbose = true "Independent set" begin + include("independent_set.jl") + end + @testset verbose = true "Fractional coloring" begin include("fractional_coloring.jl") end From 7e42f9d5783454a1220bb503117bc9893151b30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Thu, 25 Apr 2024 17:58:38 +0200 Subject: [PATCH 2/6] import function --- src/GraphsOptim.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl index 29e7e55..46bc544 100644 --- a/src/GraphsOptim.jl +++ b/src/GraphsOptim.jl @@ -6,7 +6,7 @@ A package for graph optimization algorithms that rely on mathematical programmin module GraphsOptim using Graphs: AbstractGraph, is_directed -using Graphs: vertices, edges, nv, ne, src, dst, inneighbors, outneighbors +using Graphs: vertices, edges, nv, ne, src, dst, inneighbors, outneighbors, has_edge using Graphs: complement, maximal_cliques using FillArrays: Zeros, Ones, Fill using HiGHS: HiGHS From c091a130fbd36905ae335ed49c4eae9b661b2bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Thu, 25 Apr 2024 18:15:19 +0200 Subject: [PATCH 3/6] export function --- src/GraphsOptim.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl index 46bc544..b4e75bd 100644 --- a/src/GraphsOptim.jl +++ b/src/GraphsOptim.jl @@ -24,6 +24,7 @@ export min_cost_flow export min_cost_assignment export FAQ, GOAT, graph_matching export min_vertex_cover +export maximum_independent_set export fractional_chromatic_number, fractional_clique_number export shortest_path From b8bb4ae34b51dd88b3141d09a8758b526bff0198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Thu, 25 Apr 2024 18:25:13 +0200 Subject: [PATCH 4/6] fix export --- src/GraphsOptim.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl index b4e75bd..ec187fd 100644 --- a/src/GraphsOptim.jl +++ b/src/GraphsOptim.jl @@ -24,7 +24,7 @@ export min_cost_flow export min_cost_assignment export FAQ, GOAT, graph_matching export min_vertex_cover -export maximum_independent_set +export maximum_weight_independent_set export fractional_chromatic_number, fractional_clique_number export shortest_path From 0be9e46c1d325c9f8213cfcf1eabaaf99d876a66 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:12:23 +0200 Subject: [PATCH 5/6] Add to objective --- src/independent_set.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/independent_set.jl b/src/independent_set.jl index 12d0778..39caf9e 100644 --- a/src/independent_set.jl +++ b/src/independent_set.jl @@ -19,7 +19,8 @@ function maximum_weight_independent_set!( covering_constraint[i=1:nv(g), j=1:nv(g); i ≠ j && has_edge(g, i, j)], f[i] + f[j] <= 1, ) - @objective(model, Max, dot(f, vertex_weights)) + obj = objective_function(model) + add_to_expression!(obj, dot(f, vertex_weights)) return model end From 4851bfe004b958b46d7f1a8317d773955f2e8425 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:22:50 +0200 Subject: [PATCH 6/6] Objective max --- src/independent_set.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/independent_set.jl b/src/independent_set.jl index 39caf9e..8f7762a 100644 --- a/src/independent_set.jl +++ b/src/independent_set.jl @@ -21,6 +21,7 @@ function maximum_weight_independent_set!( ) obj = objective_function(model) add_to_expression!(obj, dot(f, vertex_weights)) + @objective(model, Max, obj) return model end