Skip to content

Commit b0c7e4b

Browse files
authored
Merge pull request #13 from victorthouvenot/matching_reduction
Maximal weight matching using reduction
2 parents 76286e2 + b231d30 commit b0c7e4b

File tree

4 files changed

+418
-294
lines changed

4 files changed

+418
-294
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ w = zeros(3,3)
4545
w[1,2] = 1
4646
w[3,2] = 1
4747
w[1,3] = 1
48-
match = maximum_weight_matching(g,with_optimizer(Cbc.Optimizer, logLevel=0),w)
48+
match = maximum_weight_matching(g, optimizer_with_attributes(Cbc.Optimizer, "LogLevel" => 0),w)
4949
# match.weight ≈ 1
5050
```

src/GraphsMatching.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const MOI = MathOptInterface
1010
import BlossomV # 'using BlossomV' leads to naming conflicts with JuMP
1111
using Hungarian
1212

13-
export MatchingResult, maximum_weight_matching, maximum_weight_maximal_matching, minimum_weight_perfect_matching, HungarianAlgorithm, LPAlgorithm
13+
export MatchingResult, maximum_weight_matching, maximum_weight_matching_reduction, maximum_weight_maximal_matching, minimum_weight_perfect_matching, HungarianAlgorithm, LPAlgorithm
1414

1515
"""
1616
struct MatchingResult{U}

src/maximum_weight_matching.jl

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,40 @@ Returns MatchingResult containing:
2121
"""
2222
function maximum_weight_matching end
2323

24-
function maximum_weight_matching(g::Graph,
25-
optimizer,
26-
w::AbstractMatrix{U} = default_weights(g)) where {U <:Real}
24+
function maximum_weight_matching(
25+
g::Graph,
26+
optimizer,
27+
w::AbstractMatrix{U} = default_weights(g),
28+
) where {U<:Real}
2729

2830
model = Model(optimizer)
2931
n = nv(g)
3032
edge_list = collect(edges(g))
3133

3234
# put the edge weights in w in the right order to be compatible with edge_list
33-
for j in 1:n
34-
for i in 1:n
35-
if i > j && w[i,j] > zero(U) && w[j,i] < w[i,j]
36-
w[j,i] = w[i,j]
35+
for j = 1:n
36+
for i = 1:n
37+
if i > j && w[i, j] > zero(U) && w[j, i] < w[i, j]
38+
w[j, i] = w[i, j]
39+
end
40+
if Edge(i, j) edge_list
41+
w[i, j] = zero(U)
42+
end
3743
end
38-
if Edge(i,j) edge_list
39-
w[i,j] = zero(U)
40-
end
41-
end
4244
end
4345

4446
if is_bipartite(g)
45-
@variable(model, x[edge_list] >= 0) # no need to enforce integrality
47+
@variable(model, x[edge_list] >= 0) # no need to enforce integrality
4648
else
47-
@variable(model, x[edge_list] >= 0, Int) # requires MIP solver
49+
@variable(model, x[edge_list] >= 0, Int) # requires MIP solver
4850
end
49-
@objective(model, Max, sum(x[e]*w[src(e),dst(e)] for e in edge_list))
51+
@objective(model, Max, sum(x[e] * w[src(e), dst(e)] for e in edge_list))
5052

51-
@constraint(model, c1[i=1:n], sum(x[Edge(minmax(i,j))] for j in neighbors(g,i)) <= 1)
53+
@constraint(
54+
model,
55+
c1[i = 1:n],
56+
sum(x[Edge(minmax(i, j))] for j in neighbors(g, i)) <= 1
57+
)
5258
optimize!(model)
5359
status = JuMP.termination_status(model)
5460
status != MOI.OPTIMAL && error("JuMP solver failed to find optimal solution.")
@@ -58,22 +64,69 @@ function maximum_weight_matching(g::Graph,
5864
end
5965

6066
""" Returns an array of mates from a dictionary that maps edges to {0,1} """
61-
function dict_to_arr(n::Int64, solution::JuMP.Containers.DenseAxisArray{U,1,Tuple{Array{E,1}}}, edge_list::AbstractVector{E}) where {U<: Real, E<: Edge}
62-
mate = fill(-1,n)
63-
for e in edge_list
64-
if solution[e] >= 1 - 1e-5 # Some tolerance to numerical approximations by the solver.
65-
mate[src(e)] = dst(e)
66-
mate[dst(e)] = src(e)
67+
function dict_to_arr(
68+
n::Int64,
69+
solution::JuMP.Containers.DenseAxisArray{U,1,Tuple{Array{E,1}}},
70+
edge_list::AbstractVector{E},
71+
) where {U<:Real,E<:Edge}
72+
mate = fill(-1, n)
73+
for e in edge_list
74+
if solution[e] >= 1 - 1e-5 # Some tolerance to numerical approximations by the solver.
75+
mate[src(e)] = dst(e)
76+
mate[dst(e)] = src(e)
77+
end
6778
end
68-
end
69-
return mate
79+
return mate
7080
end
7181

7282

7383
function default_weights(g::G) where {G<:AbstractGraph}
74-
m = spzeros(nv(g),nv(g))
75-
for e in edges(g)
76-
m[src(e),dst(e)] = 1
77-
end
78-
return m
84+
m = spzeros(nv(g), nv(g))
85+
for e in edges(g)
86+
m[src(e), dst(e)] = 1
87+
end
88+
return m
89+
end
90+
91+
"""
92+
maximum_weight_matching_reduction(g::Graph, w::Matrix{Real}) -> Array{Edge}
93+
94+
Given a graph `g` and an edgemap `w` containing weights associated to edges,
95+
returns a matching with the maximum total weight.
96+
`w` is an adjacent matrix that maps edges i => j to weights.
97+
If no weight parameter is given, all edges will be considered to have weight 1
98+
99+
This algorithm uses a reduction based on the minimum_weight_perfect_matching function
100+
to find the maximum weight matching (see https://homepages.cwi.nl/~schaefer/ftp/pdf/masters-thesis.pdf section 1.5.1).
101+
102+
Return an array of edges contained in the matching.
103+
"""
104+
function maximum_weight_matching_reduction(
105+
g::Graph,
106+
w::AbstractMatrix{U} = default_weights(g),
107+
) where {U<:Real}
108+
109+
h = deepcopy(g)
110+
# collect needed since we modify the edges later
111+
edge_iter = collect(edges(h))
112+
l = nv(h)
113+
add_vertices!(h, l)
114+
weights = Dict{edgetype(g), eltype(w)}()
115+
for edge in edge_iter
116+
add_edge!(h, src(edge) + l, dst(edge) + l)
117+
weights[edge] = -w[src(edge), dst(edge)]
118+
weights[Edge(dst(edge), src(edge))] = -w[src(edge), dst(edge)]
119+
weights[Edge(src(edge) + l, dst(edge) + l)] = -w[src(edge), dst(edge)]
120+
weights[Edge(dst(edge) + l, src(edge) + l)] = -w[src(edge), dst(edge)]
121+
end
122+
for i = 1:l
123+
add_edge!(g, i, i + l)
124+
weights[Edge(i, i + l)] = 0
125+
end
126+
127+
match = minimum_weight_perfect_matching(h, weights)
128+
129+
result = [Edge(i, match.mate[i]) for i in 1:l if match.mate[i] <= l && match.mate[i] > 0]
130+
131+
return result
79132
end

0 commit comments

Comments
 (0)