diff --git a/docs/src/algorithms.md b/docs/src/algorithms.md index 657b753..ad088f9 100644 --- a/docs/src/algorithms.md +++ b/docs/src/algorithms.md @@ -61,6 +61,15 @@ min_cost_assignment GraphsOptim.min_cost_assignment! ``` +## Minimum Vertex Cover + +```@docs +min_vertex_cover +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$ + ## Graph matching !!! danger "Work in progress" diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl index e514036..dc03c46 100644 --- a/src/GraphsOptim.jl +++ b/src/GraphsOptim.jl @@ -23,6 +23,7 @@ using OptimalTransport: sinkhorn export min_cost_flow export min_cost_assignment export FAQ, GOAT, graph_matching +export min_vertex_cover export fractional_chromatic_number, fractional_clique_number export shortest_path @@ -30,6 +31,7 @@ include("utils.jl") include("flow.jl") include("assignment.jl") include("graph_matching.jl") +include("min_vertex_cover.jl") include("fractional_coloring.jl") include("shortest_path.jl") diff --git a/src/min_vertex_cover.jl b/src/min_vertex_cover.jl new file mode 100644 index 0000000..a92a47e --- /dev/null +++ b/src/min_vertex_cover.jl @@ -0,0 +1,67 @@ +""" + min_vertex_cover!( + model, g; + var_name + ) + +Modify a JuMP model by adding the variable, constraints and objective necessary to compute a minimum vertex cove of an undirected graph. + +The vertex cover indicator variable will be named `var_name` +""" +function min_vertex_cover!(model::Model, g::AbstractGraph; integer::Bool, var_name) + if is_directed(g) + throw(ArgumentError("The graph must not be directed")) + end + + ver = collect(vertices(g)) + + f = @variable(model, [ver]; integer=integer, base_name=String(var_name)) + model[Symbol(var_name)] = f + + for v in ver + @constraint(model, f[v] >= 0) + @constraint(model, f[v] <= 1) + end + + for e in edges(g) + @constraint(model, f[src(e)] + f[dst(e)] >= 1) + end + + obj = objective_function(model) + for v in ver + add_to_expression!(obj, f[v]) + end + @objective(model, Min, obj) + + return model +end + +""" + min_vertex_cover( + g, optimizer + ) + +Compute a minimum vertex cover of an undirected graph. + +# Arguments + +- `g::Graphs.AbstractGraph`: an undirected graph `G = (V, E)` + +# Keyword arguments + +- `optimizer`: JuMP-compatible solver (default is `HiGHS.Optimizer`) +""" +function min_vertex_cover(g::AbstractGraph; integer::Bool=true, optimizer=HiGHS.Optimizer) + model = Model(optimizer) + set_silent(model) + + min_vertex_cover!(model, g; integer=integer, var_name=:cover) + + optimize!(model) + @assert termination_status(model) == OPTIMAL + + cover_values = Vector(value.(model[:cover])) + cover_vertices = findall(==(1), cover_values) + + return cover_vertices +end diff --git a/test/min_vertex_cover.jl b/test/min_vertex_cover.jl new file mode 100644 index 0000000..1a5b77e --- /dev/null +++ b/test/min_vertex_cover.jl @@ -0,0 +1,18 @@ +using Graphs: SimpleGraph +using GraphsOptim +using Test + +adj_matrix = [ + 0 1 1 0 0 0 + 1 0 1 1 1 1 + 1 1 0 0 0 0 + 0 1 0 0 0 0 + 0 1 0 0 0 0 + 0 1 0 0 0 0 +] + +expected_ans_options = [Set([1, 2]), Set([2, 3])] + +graph = SimpleGraph(adj_matrix) + +@test Set(min_vertex_cover(graph)) in expected_ans_options diff --git a/test/runtests.jl b/test/runtests.jl index adfb030..8811777 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,6 +40,10 @@ using Test include("graph_matching.jl") end + @testset verbose = true "Vertex cover" begin + include("min_vertex_cover.jl") + end + @testset verbose = true "Fractional coloring" begin include("fractional_coloring.jl") end