Skip to content

Commit c6c5e92

Browse files
committed
improve typing of JuMP interface functions and add tests
1 parent 39834da commit c6c5e92

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

src/interface.jl

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ A JuMP interface to the algorithms implemented by JuMPIn
2424

2525
import JuMP
2626

27-
import JuMPIn: get_bipartite_incidence_graph, maximum_matching
27+
import JuMPIn: get_bipartite_incidence_graph, maximum_matching, GraphDataTuple
2828

2929
import Graphs as gjl
3030
import BipartiteMatching as bpm
@@ -101,7 +101,7 @@ end
101101

102102

103103
IncidenceGraphInterface(
104-
args::Tuple{Tuple, Dict, Dict}
104+
args::GraphDataTuple
105105
) = IncidenceGraphInterface(
106106
_tuple_to_graphs_jl(args[1]),
107107
args[2],
@@ -119,7 +119,7 @@ IncidenceGraphInterface(
119119

120120

121121
IncidenceGraphInterface(
122-
constraints::Vector{JuMP.ConstraintRef},
122+
constraints::Vector{<:JuMP.ConstraintRef},
123123
variables::Vector{JuMP.VariableRef},
124124
) = IncidenceGraphInterface(
125125
get_bipartite_incidence_graph(constraints, variables)
@@ -138,7 +138,7 @@ Return the variables adjacent to a constraint in an incidence graph.
138138
function get_adjacent(
139139
igraph::IncidenceGraphInterface,
140140
constraint::JuMP.ConstraintRef,
141-
)
141+
)::Vector{JuMP.VariableRef}
142142
con_node = igraph._con_node_map[constraint]
143143
var_nodes = gjl.neighbors(igraph._graph, con_node)
144144
variables = [igraph._nodes[n] for n in var_nodes]
@@ -165,7 +165,7 @@ julia> @NLconstraint(m, eq_2, v[1]*v[2]^3 == 2);
165165
julia> igraph = ji.IncidenceGraphInterface(m);
166166
julia> adj_cons = ji.get_adjacent(igraph, v[1]);
167167
julia> display(adj_cons)
168-
2-element Vector{ConstraintRef{Model, C, ScalarShape} where C}:
168+
2-element Vector{ConstraintRef}:
169169
eq_1 : v[1] + v[3] = 1.0
170170
v[1] * v[2] ^ 3.0 - 2.0 = 0
171171
```
@@ -174,7 +174,7 @@ julia> display(adj_cons)
174174
function get_adjacent(
175175
igraph::IncidenceGraphInterface,
176176
variable::JuMP.VariableRef,
177-
)
177+
)::Vector{JuMP.ConstraintRef}
178178
var_node = igraph._var_node_map[variable]
179179
con_nodes = gjl.neighbors(igraph._graph, var_node)
180180
constraints = [igraph._nodes[n] for n in con_nodes]
@@ -200,7 +200,7 @@ julia> @NLconstraint(m, eq_2, v[1]*v[2]^3 == 2);
200200
julia> igraph = ji.IncidenceGraphInterface(m);
201201
julia> matching = ji.maximum_matching(igraph);
202202
julia> display(matching)
203-
Dict{ConstraintRef{Model, C, ScalarShape} where C, VariableRef} with 2 entries:
203+
Dict{ConstraintRef, VariableRef} with 2 entries:
204204
v[1] * v[2] ^ 3.0 - 2.0 = 0 => v[2]
205205
eq_1 : v[1] + v[3] = 1.0 => v[1]
206206
```
@@ -220,13 +220,32 @@ end
220220

221221

222222
function maximum_matching(
223-
constraints::Vector{JuMP.ConstraintRef},
223+
constraints::Vector{<:JuMP.ConstraintRef},
224224
variables::Vector{JuMP.VariableRef},
225225
)::Dict{JuMP.ConstraintRef, JuMP.VariableRef}
226226
igraph = IncidenceGraphInterface(constraints, variables)
227227
return maximum_matching(igraph)
228228
end
229229

230+
const DMConPartition = NamedTuple{
231+
(:underconstrained, :square, :overconstrained, :unmatched),
232+
Tuple{
233+
Vector{JuMP.ConstraintRef},
234+
Vector{JuMP.ConstraintRef},
235+
Vector{JuMP.ConstraintRef},
236+
Vector{JuMP.ConstraintRef},
237+
},
238+
}
239+
240+
const DMVarPartition = NamedTuple{
241+
(:unmatched, :underconstrained, :square, :overconstrained),
242+
Tuple{
243+
Vector{JuMP.VariableRef},
244+
Vector{JuMP.VariableRef},
245+
Vector{JuMP.VariableRef},
246+
Vector{JuMP.VariableRef},
247+
},
248+
}
230249

231250
"""
232251
dulmage_mendelsohn(igraph::IncidenceGraphInterface)
@@ -294,21 +313,23 @@ julia> display(var_dmp.underconstrained)
294313
v[1]
295314
v[2]
296315
julia> display(con_dmp.underconstrained)
297-
2-element Vector{ConstraintRef{Model, C, ScalarShape} where C}:
316+
2-element Vector{ConstraintRef}:
298317
eq_1 : v[1] + v[3] = 1.0
299318
v[1] * v[2] ^ 3.0 - 2.0 = 0
300319
julia> display(var_dmp.square)
301320
1-element Vector{VariableRef}:
302321
v[4]
303322
julia> display(con_dmp.square)
304-
1-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarQuadraticFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
323+
1-element Vector{ConstraintRef}:
305324
eq_3 : v[4]² = 3.0
306325
julia> # As there are no unmatched constraints, the overconstrained subsystem
307326
julia> # is empty.
308327
```
309328
310329
"""
311-
function dulmage_mendelsohn(igraph::IncidenceGraphInterface)
330+
function dulmage_mendelsohn(
331+
igraph::IncidenceGraphInterface
332+
)::Tuple{DMConPartition, DMVarPartition}
312333
ncon = length(igraph._con_node_map)
313334
con_node_set = Set(1:ncon)
314335
con_dmp, var_dmp = dulmage_mendelsohn(igraph._graph, con_node_set)
@@ -332,9 +353,9 @@ end
332353

333354

334355
function dulmage_mendelsohn(
335-
constraints::Vector{JuMP.ConstraintRef},
356+
constraints::Vector{<:JuMP.ConstraintRef},
336357
variables::Vector{JuMP.VariableRef},
337-
)
358+
)::Tuple{DMConPartition, DMVarPartition}
338359
igraph = IncidenceGraphInterface(constraints, variables)
339360
return dulmage_mendelsohn(igraph)
340361
end

test/interface.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ function test_overconstrained_due_to_fixed_variable()
236236
@test length(var_dmp.overconstrained) == 2
237237
@test length(con_dmp.overconstrained) == 2
238238
@test length(con_dmp.unmatched) == 1
239+
return
239240
end
240241

241242
function test_overconstrained_due_to_including_bound()
@@ -249,6 +250,52 @@ function test_overconstrained_due_to_including_bound()
249250
@test length(var_dmp.overconstrained) == 2
250251
@test length(con_dmp.overconstrained) == 2
251252
@test length(con_dmp.unmatched) == 1
253+
return
254+
end
255+
256+
function test_interface_from_constraints_and_variables()
257+
m = JuMP.Model()
258+
@JuMP.variable(m, x[1:3])
259+
@JuMP.constraint(m, eq1, x[1] + x[2] == 2)
260+
@JuMP.constraint(m, eq2, x[3]*x[2] == 1.1)
261+
constraints = [eq1, eq2]
262+
variables = [x[1], x[3]]
263+
igraph = ji.IncidenceGraphInterface(constraints, variables)
264+
_test_igraph_fields(igraph, constraints, variables)
265+
return
266+
end
267+
268+
function test_matching_from_constraints_and_variables()
269+
m = JuMP.Model()
270+
@JuMP.variable(m, x[1:3])
271+
@JuMP.constraint(m, eq1, x[1] + x[2] == 2)
272+
@JuMP.constraint(m, eq2, x[3]*x[2] == 1.1)
273+
constraints = [eq1, eq2]
274+
variables = [x[1], x[3]]
275+
matching = ji.maximum_matching(constraints, variables)
276+
@test length(matching) == 2
277+
@test matching[eq1] == x[1]
278+
@test matching[eq2] == x[3]
279+
return
280+
end
281+
282+
function test_dulmage_mendelsohn_from_constraints_and_variables()
283+
m = JuMP.Model()
284+
@JuMP.variable(m, x[1:3])
285+
@JuMP.constraint(m, eq1, x[1] + x[2] == 2)
286+
@JuMP.constraint(m, eq2, x[3]*x[2] == 1.1)
287+
constraints = [eq1, eq2]
288+
variables = [x[1], x[3]]
289+
con_dmp, var_dmp = ji.dulmage_mendelsohn(constraints, variables)
290+
@test con_dmp.unmatched == []
291+
@test con_dmp.underconstrained == []
292+
@test con_dmp.overconstrained == []
293+
@test Set(con_dmp.square) == Set(constraints)
294+
@test var_dmp.unmatched == []
295+
@test var_dmp.underconstrained == []
296+
@test var_dmp.overconstrained == []
297+
@test Set(var_dmp.square) == Set(variables)
298+
return
252299
end
253300

254301
function runtests()
@@ -262,6 +309,9 @@ function runtests()
262309
test_dulmage_mendelsohn()
263310
test_overconstrained_due_to_fixed_variable()
264311
test_overconstrained_due_to_including_bound()
312+
test_interface_from_constraints_and_variables()
313+
test_matching_from_constraints_and_variables()
314+
test_dulmage_mendelsohn_from_constraints_and_variables()
265315
end
266316

267317
end

0 commit comments

Comments
 (0)