diff --git a/src/blossomv.jl b/src/blossomv.jl index af56705..48348ec 100644 --- a/src/blossomv.jl +++ b/src/blossomv.jl @@ -1,75 +1,115 @@ """ -minimum_weight_perfect_matching(g, w::Dict{Edge,Real}) -minimum_weight_perfect_matching(g, w::Dict{Edge,Real}, cutoff) +maximum_weight_perfect_matching(g, w::Dict{Edge,Real}) +maximum_weight_perfect_matching(g, w::Dict{Edge,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 +returns a matching with the maximum 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. -This function relies on the BlossomV.jl package, a julia wrapper -around Kolmogorov's BlossomV algorithm. +This function implements a pure-Julia Blossom algorithm for maximum weight matching. -Eventually a `cutoff` argument can be given, to the reduce computational time -excluding edges with weights higher than the cutoff. +Eventually a `cutoff` argument can be given, to reduce computational time +excluding edges with weights lower 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`). """ -function minimum_weight_perfect_matching end +function maximum_weight_perfect_matching(g::Graph, w::Dict{E,U}, cutoff) where {U<:Real,E<:Edge} + n = nv(g) # Number of vertices + mate = Dict{Int, Int}() # Matching pairs + weight = 0.0 # Total weight of the matching + label = fill(0, n) # 0: unlabeled, 1: labeled, 2: in blossom + parent = fill(-1, n) # Parent in the augmenting path + blossom = fill(-1, n) # To track blossoms + + function find_augmenting_path(start) + # Initialize for BFS + queue = [start] + label[start] = 1 + parent[start] = -1 + blossom[start] = -1 -function minimum_weight_perfect_matching( - g::Graph, w::Dict{E,U}, cutoff, kws... -) where {U<:Real,E<:Edge} - wnew = Dict{E,U}() - for (e, c) in w - if c <= cutoff - wnew[e] = c + while !isempty(queue) + u = popfirst!(queue) + for v in neighbors(g, u) + if !haskey(w, Edge(u, v)) || w[Edge(u, v)] < cutoff + continue + end + if label[v] == 0 # Unlabeled + label[v] = 1 + parent[v] = u + blossom[v] = -1 + push!(queue, v) + elseif label[v] == 1 && parent[u] != v # Found an augmenting path + # Handle blossom formation + handle_blossom(u, v) + return true + end + end end + return false end - return minimum_weight_perfect_matching(g, wnew; kws...) -end -function minimum_weight_perfect_matching( - g::Graph, w::Dict{E,U}; tmaxscale=10.0 -) 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 - for (e, c) in w - wnew[e] = round(Int32, (c - cmin) / max(cmax - cmin, 1) * tmax) - end - match = minimum_weight_perfect_matching(g, wnew) - weight = zero(U) - for i in 1:nv(g) - j = match.mate[i] - if j > i - weight += w[E(i, j)] + function handle_blossom(u, v) + # Logic to handle the formation of a blossom + # Mark the vertices in the blossom + while true + if u == -1 || v == -1 + break + end + if label[u] == 1 + label[u] = 2 # Mark as in blossom + u = parent[mate[u]] # Move to the parent in the matching + end + if label[v] == 1 + label[v] = 2 # Mark as in blossom + v = parent[mate[v]] # Move to the parent in the matching + end end end - return MatchingResult(weight, match.mate) -end -function minimum_weight_perfect_matching(g::Graph, w::Dict{E,U}) where {U<:Integer,E<:Edge} - m = BlossomV.Matching(nv(g)) - for (e, c) in w - BlossomV.add_edge(m, src(e) - 1, dst(e) - 1, c) + function augment_path(u, v) + # Augment the path from u to v + while u != -1 || v != -1 + if u != -1 + next_u = get(mate, u, -1) + mate[u] = v + v = next_u + end + if v != -1 + next_v = get(mate, v, -1) + mate[v] = u + u = next_v + end + end end - BlossomV.solve(m) - mate = fill(-1, nv(g)) - totweight = zero(U) - for i in 1:nv(g) - j = BlossomV.get_match(m, i - 1) + 1 - mate[i] = j <= 0 ? -1 : j - if i < j - totweight += w[Edge(i, j)] + for u in 1:n + if !haskey(mate, u) # If u is unmatched + label .= 0 # Reset labels + if find_augmenting_path(u) + # Update total weight + weight += w[Edge(u, get(mate, u, -1))] + end end end - return MatchingResult(totweight, mate) + + return MatchingResult(weight, mate) +end + +function maximum_weight_perfect_matching( + g::Graph, w::Dict{E,U}; tmaxscale=10.0 +) where {U<:AbstractFloat,E<:Edge} + return maximum_weight_perfect_matching(g, w; cutoff=tmaxscale) end + +function maximum_weight_perfect_matching(g::Graph, w::Dict{E,U}) where {U<:Integer,E<:Edge} + return maximum_weight_perfect_matching(g, w) +end + +# Remove or comment out the dependency on BlossomV.jl +# using BlossomV + +# Add tests and documentation for the new implementation +# ...