diff --git a/src/blossomv.jl b/src/blossomv.jl index da1e2d4..545893e 100644 --- a/src/blossomv.jl +++ b/src/blossomv.jl @@ -1,6 +1,6 @@ """ - 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::Dict{Edge,U<:Real}) + minimum_weight_perfect_matching(g, w::Dict{Edge,U<:Real}, cutoff) 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 @@ -16,7 +16,9 @@ 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`). +If there is no matching, the returned weight will be `typemax(U)` and no vertex will be matched + +In case of error try to change the optional argument `tmaxscale` (default is `tmaxscale=2*nv(g)`). """ function minimum_weight_perfect_matching end @@ -30,16 +32,21 @@ function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}, cutoff, kws...) return minimum_weight_perfect_matching(g, wnew; kws...) end -function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}; tmaxscale=10.) where {U<:AbstractFloat, E<:Edge} +function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}; tmaxscale=2*nv(g)) where {U<:AbstractFloat, E<:Edge} wnew = Dict{E, Int32}() cmax = maximum(values(w)) cmin = minimum(values(w)) - tmax = typemax(Int32) / tmaxscale # /10 is kinda arbitrary, - # hopefully high enough to not occur in overflow problems + + + tmax = typemax(Int32) / ( (cmax-cmin) * tmaxscale) # tmaxscale = 2*nv(g) is made to ensure that the weights are sufficiently small + # compared to typemax(Int32)/3, which is used as an infinite value for non edges for (e, c) in w - wnew[e] = round(Int32, (c-cmin) / (cmax-cmin) * tmax) + wnew[e] = round(Int32, (c-cmin) * tmax) end match = minimum_weight_perfect_matching(g, wnew) + if match.mate[1] == -1 # there is no matching + return MatchingResult(typemax(U), match.mate) + end weight = zero(U) for i=1:nv(g) j = match.mate[i] @@ -51,18 +58,35 @@ function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}; tmaxscale=10.) end function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}) where {U<:Integer, E<:Edge} + @assert nv(g) % 2 == 0 m = BlossomV.Matching(nv(g)) for (e, c) in w BlossomV.add_edge(m, src(e)-1, dst(e)-1, c) end + + # Blossom V needs a feasible matching to work, so we add dummy edges + tmax = round(Int, typemax(Int32) / 3) + for i in 1:round(Int, nv(g)/2) + u, v = 2i-1, 2i + if Edge(u, v) ∉ keys(w) && Edge(v, u) ∉ keys(w) + BlossomV.add_edge(m, u-1, v-1, tmax) + end + end + BlossomV.solve(m) mate = fill(-1, nv(g)) totweight = zero(U) for i=1:nv(g) j = BlossomV.get_match(m, i-1) + 1 + if j == -1 # there is no matching, so we return an infinite weight + return MatchingResult(typemax(U), fill(-1, nv(g))) + end mate[i] = j <= 0 ? -1 : j if i < j + if !haskey(w, Edge(i, j)) # there is no matching, so we return an infinite weight + return MatchingResult(typemax(U), fill(-1, nv(g))) + end totweight += w[Edge(i,j)] end end diff --git a/test/runtests.jl b/test/runtests.jl index 0beb14f..f00da4f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -254,6 +254,33 @@ end @test match.mate[2] == 3 @test match.mate[3] == 2 @test match.weight ≈ 11.5 + + # no existing matching + g = complete_graph(4) + w = Dict{Edge,Int}() + w[Edge(1,2)] = 10 + w[Edge(1,3)] = 20 + w[Edge(1,4)] = 40 + + match = minimum_weight_perfect_matching(g, w) + @test match.mate[1] == -1 + @test match.mate[4] == -1 + @test match.mate[2] == -1 + @test match.mate[3] == -1 + @test match.weight == typemax(Int) + + g = complete_graph(4) + w = Dict{Edge,Float64}() + w[Edge(1,2)] = 0.5 + w[Edge(1,3)] = 10.2 + w[Edge(1,4)] = -5.6 + + match = minimum_weight_perfect_matching(g, w) + @test match.mate[1] == -1 + @test match.mate[4] == -1 + @test match.mate[2] == -1 + @test match.mate[3] == -1 + @test match.weight == typemax(Float64) end