diff --git a/Project.toml b/Project.toml index 5783ce8..a559d4b 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +WatchJuliaBurn = "952999c0-2bc0-44cb-b633-dc1e299b187b" [compat] BlossomV = "0.4" @@ -18,6 +19,7 @@ Graphs = "1.4, 1.7" Hungarian = "0.4, 0.6" JuMP = "1" MathOptInterface = "1" +WatchJuliaBurn = "0.2.0" julia = "1" [extras] diff --git a/src/blossomv.jl b/src/blossomv.jl index da1e2d4..29e2bfc 100644 --- a/src/blossomv.jl +++ b/src/blossomv.jl @@ -1,19 +1,12 @@ """ - minimum_weight_perfect_matching(g, w::Dict{Edge,Real}) - minimum_weight_perfect_matching(g, w::Dict{Edge,Real}, cutoff) + minimum_weight_perfect_matching(g, w::AbstractMatrix{U}) -Given a graph `g` and an edgemap `w` containing weights associated to edges, -returns a matching with the mimimum total weight among the ones containing -exactly `nv(g)/2` edges. - -Edges in `g` not present in `w` will not be considered for the matching. +Given a graph `g` and weights `w`, returns a matching with the mimimum total +weight among the ones containing exactly `nv(g)/2` edges. This function relies on the BlossomV.jl package, a julia wrapper around Kolmogorov's BlossomV algorithm. -Eventually a `cutoff` argument can be given, to the reduce computational time -excluding edges with weights higher than the cutoff. - The returned object is of type `MatchingResult`. In case of error try to change the optional argument `tmaxscale` (default is `tmaxscale=10`). @@ -68,3 +61,22 @@ function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}) where {U<:Integ end return MatchingResult(totweight, mate) end + +function minimum_weight_perfect_matching(g::Graph, w::AbstractMatrix{U}, cutoff, kws...) where {U <: Real} + wnew = Dict{Edge, U}() + for e in edges(g) + c = w[src(e), dst(e)] + if c <= cutoff + wnew[e] = c + end + end + return minimum_weight_perfect_matching(g, wnew, kws...) +end + +function minimum_weight_perfect_matching(g::Graph, w::AbstractMatrix{U} = default_weights(g), kws...) where {U <: Real} + wnew = Dict{Edge, U}() + for e in edges(g) + wnew[e] = w[src(e), dst(e)] + end + return minimum_weight_perfect_matching(g, wnew, kws...) +end diff --git a/test/runtests.jl b/test/runtests.jl index 0beb14f..31dc01b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,255 +6,357 @@ using JuMP using LinearAlgebra: I @testset "GraphsMatching" begin - -@testset "maximum_weight_matching" begin - g = complete_graph(3) - w = [ - 1 2 1 - 1 1 1 - 3 1 1 + @testset "maximum_weight_matching" begin + g = complete_graph(3) + w = [ + 1 2 1 + 1 1 1 + 3 1 1 ] - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w) - @test match.mate[1] == 3 - @test match.weight ≈ 3 - - g = complete_graph(3) - w = zeros(3,3) - w[1,2] = 1 - w[3,2] = 1 - w[1,3] = 1 - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w) - @test match.weight ≈ 1 - - - g = Graph(4) - add_edge!(g, 1,3) - add_edge!(g, 1,4) - add_edge!(g, 2,4) - - w =zeros(4,4) - w[1,3] = 1 - w[1,4] = 3 - w[2,4] = 1 - - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w) - @test match.weight ≈ 3 - @test match.mate[1] == 4 - @test match.mate[2] == -1 - @test match.mate[3] == -1 - @test match.mate[4] == 1 - - g = Graph(4) - add_edge!(g, 1,2) - add_edge!(g, 2,3) - add_edge!(g, 3,1) - add_edge!(g, 3,4) - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0)) - @test match.weight ≈ 2 - @test match.mate[1] == 2 - @test match.mate[2] == 1 - @test match.mate[3] == 4 - @test match.mate[4] == 3 - - w = zeros(4,4) - w[1,2] = 1 - w[2,3] = 1 - w[1,3] = 1 - w[3,4] = 1 - - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w) - @test match.weight ≈ 2 - @test match.mate[1] == 2 - @test match.mate[2] == 1 - @test match.mate[3] == 4 - @test match.mate[4] == 3 - - w = zeros(4,4) - w[1,2] = 1 - w[2,3] = 1 - w[1,3] = 5 - w[3,4] = 1 - - match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w) - @test match.weight ≈ 5 - @test match.mate[1] == 3 - @test match.mate[2] == -1 - @test match.mate[3] == 1 - @test match.mate[4] == -1 -end - - -@testset "maximum_weight_maximal_matching" begin - - g = complete_bipartite_graph(2,2) - w = zeros(4,4) - w[1,3] = 10. - w[1,4] = 1. - w[2,3] = 2. - w[2,4] = 11. - match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0)) - @test match.weight ≈ 21 - @test match.mate[1] == 3 - @test match.mate[3] == 1 - @test match.mate[2] == 4 - @test match.mate[4] == 2 - - g =complete_bipartite_graph(2,4) - w =zeros(6,6) - w[1,3] = 10 - w[1,4] = 0.5 - w[2,3] = 11 - w[2,4] = 1 - match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0)) - @test match.weight ≈ 11.5 - @test match.mate[1] == 4 - @test match.mate[4] == 1 - @test match.mate[2] == 3 - @test match.mate[3] == 2 - - g =complete_bipartite_graph(2,6) - w =zeros(8,8) - w[1,3] = 10 - w[1,4] = 0.5 - w[2,3] = 11 - w[2,4] = 1 - w[2,5] = -1 - w[2,6] = -1 - match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), cutoff=0) - @test match.weight ≈ 11.5 - @test match.mate[1] == 4 - @test match.mate[4] == 1 - @test match.mate[2] == 3 - @test match.mate[3] == 2 - - g =complete_bipartite_graph(4,2) - w = zeros(6,6) - w[3,5] = 10 - w[3,6] = 0.5 - w[2,5] = 11 - w[1,6] = 1 - w[1,5] = -1 - - match = maximum_weight_maximal_matching(g, w, algorithm=LPAlgorithm(), optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), cutoff=0) - @test match.weight ≈ 12 - @test match.mate[1] == 6 - @test match.mate[2] == 5 - @test match.mate[3] == -1 - @test match.mate[4] == -1 - @test match.mate[5] == 2 - @test match.mate[6] == 1 - - - g = complete_bipartite_graph(2, 2) - w = zeros(4, 4) - w[1, 3] = 10. - w[1, 4] = 1. - w[2, 3] = 2. - w[2, 4] = 11. - match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm()) - @test match.weight ≈ 21 - @test match.mate[1] == 3 - @test match.mate[3] == 1 - @test match.mate[2] == 4 - @test match.mate[4] == 2 - - g = complete_graph(3) - w = zeros(3, 3) - @test ! is_bipartite(g) - @test_throws ErrorException maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm()) - - g = complete_bipartite_graph(2, 4) - w = zeros(6, 6) - w[1, 3] = 10 - w[1, 4] = 0.5 - w[2, 3] = 11 - w[2, 4] = 1 - match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm()) - @test match.weight ≈ 11.5 - - g = Graph(4) - add_edge!(g, 1, 3) - add_edge!(g, 1, 4) - add_edge!(g, 2, 4) - w = zeros(4, 4) - w[1, 3] = 1 - w[1, 4] = 3 - w[2, 4] = 1 - match = maximum_weight_maximal_matching(g, w, algorithm=HungarianAlgorithm()) - @test match.weight ≈ 2 - -end - - - -@testset "minimum_weight_perfect_matching" begin - - w = Dict(Edge(1,2)=> 500) - g =Graph(2) - add_edge!(g,1,2) - match = minimum_weight_perfect_matching(g, w) - @test match.mate[1] == 2 - - - w=Dict( Edge(1,2)=>500, - Edge(1,3)=>600, - Edge(2,3)=>700, - Edge(3,4)=>100, - Edge(2,4)=>1000) - - g = complete_graph(4) - match = minimum_weight_perfect_matching(g, w) - @test match.mate[1] == 2 - @test match.mate[2] == 1 - @test match.mate[3] == 4 - @test match.mate[4] == 3 - @test match.weight ≈ 600 - - w = Dict( - Edge(1, 2) => 500, - Edge(1, 3) => 400, - Edge(2, 3) => 300, - Edge(3, 4) => 1000, - Edge(2, 4) => 1000 - ) - g = complete_graph(4) - match = minimum_weight_perfect_matching(g, w) - @test match.mate[1] == 3 - @test match.mate[2] == 4 - @test match.mate[3] == 1 - @test match.mate[4] == 2 - @test match.weight ≈ 1400 - - g =complete_bipartite_graph(2,2) - w =Dict{Edge,Float64}() - w[Edge(1,3)] = -10 - w[Edge(1,4)] = -0.5 - w[Edge(2,3)] = -11 - w[Edge(2,4)] = -1 - - match = minimum_weight_perfect_matching(g, w) - @test match.mate[1] == 4 - @test match.mate[4] == 1 - @test match.mate[2] == 3 - @test match.mate[3] == 2 - @test match.weight ≈ -11.5 - - - g = complete_graph(4) - w = Dict{Edge,Float64}() - w[Edge(1,3)] = 10 - w[Edge(1,4)] = 0.5 - w[Edge(2,3)] = 11 - w[Edge(2,4)] = 2 - w[Edge(1,2)] = 100 - - match = minimum_weight_perfect_matching(g, w, 50) - @test match.mate[1] == 4 - @test match.mate[4] == 1 - @test match.mate[2] == 3 - @test match.mate[3] == 2 - @test match.weight ≈ 11.5 -end - - + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w + ) + @test match.mate[1] == 3 + @test match.weight ≈ 3 + + g = complete_graph(3) + w = zeros(3, 3) + w[1, 2] = 1 + w[3, 2] = 1 + w[1, 3] = 1 + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w + ) + @test match.weight ≈ 1 + + g = Graph(4) + add_edge!(g, 1, 3) + add_edge!(g, 1, 4) + add_edge!(g, 2, 4) + + w = zeros(4, 4) + w[1, 3] = 1 + w[1, 4] = 3 + w[2, 4] = 1 + + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w + ) + @test match.weight ≈ 3 + @test match.mate[1] == 4 + @test match.mate[2] == -1 + @test match.mate[3] == -1 + @test match.mate[4] == 1 + + g = Graph(4) + add_edge!(g, 1, 2) + add_edge!(g, 2, 3) + add_edge!(g, 3, 1) + add_edge!(g, 3, 4) + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0) + ) + @test match.weight ≈ 2 + @test match.mate[1] == 2 + @test match.mate[2] == 1 + @test match.mate[3] == 4 + @test match.mate[4] == 3 + + w = zeros(4, 4) + w[1, 2] = 1 + w[2, 3] = 1 + w[1, 3] = 1 + w[3, 4] = 1 + + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w + ) + @test match.weight ≈ 2 + @test match.mate[1] == 2 + @test match.mate[2] == 1 + @test match.mate[3] == 4 + @test match.mate[4] == 3 + + w = zeros(4, 4) + w[1, 2] = 1 + w[2, 3] = 1 + w[1, 3] = 5 + w[3, 4] = 1 + + match = maximum_weight_matching( + g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), w + ) + @test match.weight ≈ 5 + @test match.mate[1] == 3 + @test match.mate[2] == -1 + @test match.mate[3] == 1 + @test match.mate[4] == -1 + end + + @testset "maximum_weight_maximal_matching" begin + g = complete_bipartite_graph(2, 2) + w = zeros(4, 4) + w[1, 3] = 10.0 + w[1, 4] = 1.0 + w[2, 3] = 2.0 + w[2, 4] = 11.0 + match = maximum_weight_maximal_matching( + g, + w; + algorithm=LPAlgorithm(), + optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), + ) + @test match.weight ≈ 21 + @test match.mate[1] == 3 + @test match.mate[3] == 1 + @test match.mate[2] == 4 + @test match.mate[4] == 2 + + g = complete_bipartite_graph(2, 4) + w = zeros(6, 6) + w[1, 3] = 10 + w[1, 4] = 0.5 + w[2, 3] = 11 + w[2, 4] = 1 + match = maximum_weight_maximal_matching( + g, + w; + algorithm=LPAlgorithm(), + optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), + ) + @test match.weight ≈ 11.5 + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + + g = complete_bipartite_graph(2, 6) + w = zeros(8, 8) + w[1, 3] = 10 + w[1, 4] = 0.5 + w[2, 3] = 11 + w[2, 4] = 1 + w[2, 5] = -1 + w[2, 6] = -1 + match = maximum_weight_maximal_matching( + g, + w; + algorithm=LPAlgorithm(), + optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), + cutoff=0, + ) + @test match.weight ≈ 11.5 + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + + g = complete_bipartite_graph(4, 2) + w = zeros(6, 6) + w[3, 5] = 10 + w[3, 6] = 0.5 + w[2, 5] = 11 + w[1, 6] = 1 + w[1, 5] = -1 + + match = maximum_weight_maximal_matching( + g, + w; + algorithm=LPAlgorithm(), + optimizer=optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0), + cutoff=0, + ) + @test match.weight ≈ 12 + @test match.mate[1] == 6 + @test match.mate[2] == 5 + @test match.mate[3] == -1 + @test match.mate[4] == -1 + @test match.mate[5] == 2 + @test match.mate[6] == 1 + + g = complete_bipartite_graph(2, 2) + w = zeros(4, 4) + w[1, 3] = 10.0 + w[1, 4] = 1.0 + w[2, 3] = 2.0 + w[2, 4] = 11.0 + match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm()) + @test match.weight ≈ 21 + @test match.mate[1] == 3 + @test match.mate[3] == 1 + @test match.mate[2] == 4 + @test match.mate[4] == 2 + + g = complete_graph(3) + w = zeros(3, 3) + @test !is_bipartite(g) + @test_throws ErrorException maximum_weight_maximal_matching( + g, w, algorithm=HungarianAlgorithm() + ) + + g = complete_bipartite_graph(2, 4) + w = zeros(6, 6) + w[1, 3] = 10 + w[1, 4] = 0.5 + w[2, 3] = 11 + w[2, 4] = 1 + match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm()) + @test match.weight ≈ 11.5 + + g = Graph(4) + add_edge!(g, 1, 3) + add_edge!(g, 1, 4) + add_edge!(g, 2, 4) + w = zeros(4, 4) + w[1, 3] = 1 + w[1, 4] = 3 + w[2, 4] = 1 + match = maximum_weight_maximal_matching(g, w; algorithm=HungarianAlgorithm()) + @test match.weight ≈ 2 + end + + @testset "minimum_weight_perfect_matching" begin + wmat = [ + 0 500 + 500 0 + ] + wdict = Dict(Edge(1, 2) => 500) + g = Graph(2) + add_edge!(g, 1, 2) + match = minimum_weight_perfect_matching(g, wmat) + @test match.mate[1] == 2 + match = minimum_weight_perfect_matching(g, wdict) + @test match.mate[1] == 2 + + wmat = [ + 0 500 600 0 + 500 0 700 1000 + 600 700 0 100 + 0 1000 100 0 + ] + g = Graph([ + Edge(1, 2), + Edge(1, 3), + Edge(2, 3), + Edge(3, 4), + Edge(2, 4) + ]) + match = minimum_weight_perfect_matching(g, wmat) + @test match.mate[1] == 2 + @test match.mate[2] == 1 + @test match.mate[3] == 4 + @test match.mate[4] == 3 + @test match.weight ≈ 600 + + wdict = Dict( + Edge(1, 2) => 500, + Edge(1, 3) => 600, + Edge(2, 3) => 700, + Edge(3, 4) => 100, + Edge(2, 4) => 1000, + ) + g = Graph(4) + match = minimum_weight_perfect_matching(g, wdict) + @test match.mate[1] == 2 + @test match.mate[2] == 1 + @test match.mate[3] == 4 + @test match.mate[4] == 3 + @test match.weight ≈ 600 + + wmat = [ + 0 500 400 0 + 500 0 300 1000 + 400 300 0 1000 + 0 1000 1000 0 + ] + g = Graph([ + Edge(1, 2), + Edge(1, 3), + Edge(2, 3), + Edge(3, 4), + Edge(2, 4), + ]) + match = minimum_weight_perfect_matching(g, wmat) + @test match.mate[1] == 3 + @test match.mate[2] == 4 + @test match.mate[3] == 1 + @test match.mate[4] == 2 + @test match.weight ≈ 1400 + + wdict = Dict( + Edge(1, 2) => 500, + Edge(1, 3) => 400, + Edge(2, 3) => 300, + Edge(3, 4) => 1000, + Edge(2, 4) => 1000, + ) + g = complete_graph(4) + match = minimum_weight_perfect_matching(g, wdict) + @test match.mate[1] == 3 + @test match.mate[2] == 4 + @test match.mate[3] == 1 + @test match.mate[4] == 2 + @test match.weight ≈ 1400 + + wmat = Matrix{Float64}([ + 0 0 -10 -0.5 + 0 0 -11 -1 + -10 -11 0 0 + -0.5 -1 0 0 + ]) + wdict= Dict{Edge,Float64}( + Edge(1, 3) => -10, + Edge(1, 4) => -0.5, + Edge(2, 3) => -11, + Edge(2, 4) => -1 + ) + g = complete_bipartite_graph(2, 2) + match = minimum_weight_perfect_matching(g, wmat) + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + @test match.weight ≈ -11.5 + match = minimum_weight_perfect_matching(g, wdict) + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + @test match.weight ≈ -11.5 + + wmat = Matrix{Float64}([ + 0 100 10 0.5 + 100 0 11 2 + 10 11 0 0 + 0.5 2 0 0 + ]) + g = Graph([ + Edge(1, 3), + Edge(1, 4), + Edge(2, 3), + Edge(2, 4), + Edge(1, 2) + ]) + match = minimum_weight_perfect_matching(g, wmat, 50) + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + @test match.weight ≈ 11.5 + + wdict = Dict{Edge,Float64}( + Edge(1, 3) => 10, + Edge(1, 4) => 0.5, + Edge(2, 3) => 11, + Edge(2, 4) => 2, + Edge(1, 2) => 100 + ) + g = complete_graph(4) + match = minimum_weight_perfect_matching(g, wdict, 50) + @test match.mate[1] == 4 + @test match.mate[4] == 1 + @test match.mate[2] == 3 + @test match.mate[3] == 2 + @test match.weight ≈ 11.5 + end end