Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit with Blossom algorithm implementation #15

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 92 additions & 52 deletions src/blossomv.jl
Original file line number Diff line number Diff line change
@@ -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
# ...