Skip to content

Commit c3f1f0d

Browse files
authored
Merge pull request #3 from lanl-ansi/include-var-in-set
Include variable-in-set constraints
2 parents 96d57c7 + e464682 commit c3f1f0d

File tree

7 files changed

+108
-16
lines changed

7 files changed

+108
-16
lines changed

src/get_equality.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ julia> display(eq_cons)
154154
function get_equality_constraints(model::JuMP.Model)::Vector{JuMP.ConstraintRef}
155155
constraints = JuMP.all_constraints(
156156
model,
157-
include_variable_in_set_constraints=false,
157+
include_variable_in_set_constraints=true,
158158
)
159159
return get_equality_constraints(constraints)
160160
end

src/identify_variables.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,16 @@ Each variable appears at most one time in the returned vector.
8383
function identify_unique_variables(
8484
model::JuMP.Model; include_inequality::Bool=false,
8585
)::Vector{JuMP.VariableRef}
86+
# Note that this method exists mostly for convenience, and is not used
87+
# by any of the "upstream" methods, e.g. get_bipartite_incidence_graph
8688
if include_inequality
8789
# Note that this may include some constraints which are not compatible
8890
# with downstream function calls (e.g. constraints where the function
8991
# and terms are vectors). We will allow downstream errors to be raised
9092
# rather than silently ignoring these constraints.
9193
constraints = JuMP.all_constraints(
9294
model,
93-
# TODO: Should this be an optional argument to this function?
94-
include_variable_in_set_constraints=false,
95+
include_variable_in_set_constraints=true,
9596
)
9697
else
9798
constraints = get_equality_constraints(model)
@@ -227,6 +228,12 @@ function identify_unique_variables(
227228
))
228229
end
229230

231+
function identify_unique_variables(
232+
var::MOI.VariableIndex
233+
)::Vector{MOI.VariableIndex}
234+
return [var]
235+
end
236+
230237

231238
"""
232239
_get_variable_terms(fcn)

src/incidence_graph.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,17 @@ function get_bipartite_incidence_graph(
8787
include_inequality::Bool = false,
8888
)
8989
if include_inequality
90+
# Note that this may generate some constraints that are incompatible
91+
# with downstream function calls (e.g. constraints involving vector
92+
# expressions).
93+
#
94+
# This is also repeated in identify_unique_variables(Model).
95+
# Identifying all constraints (including inequalities, but probably
96+
# not including VectorFunction constraints) may be something we want
97+
# to standardize at some point. E.g. a get_scalar_constraints function.
9098
constraints = JuMP.all_constraints(
9199
model,
92-
# TODO: Should this be an optional argument to this function?
93-
include_variable_in_set_constraints=false,
100+
include_variable_in_set_constraints = true,
94101
)
95102
else
96103
constraints = get_equality_constraints(model)

test/get_equality.jl

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# ___________________________________________________________________________
1919

2020
module TestGetEquality
21-
import JuMP as jmp
21+
import JuMP
2222
import MathOptInterface as moi
2323
using Test: @test, @test_throws
2424
using JuMPIn: get_equality_constraints
@@ -28,9 +28,9 @@ include("models.jl") # Models
2828
function get_flow_model_with_inequalities()
2929
m = Models.make_degenerate_flow_model()
3030
# TODO: Some assertions with the @assert macro
31-
@jmp.constraint(m, ineq1, m[:x][1] >= 0)
32-
@jmp.constraint(m, ineq2, m[:x][1]^2 + m[:x][2]^2 <= 0.5)
33-
@jmp.NLconstraint(m, ineq3, sqrt(m[:x][3]) >= 0.1)
31+
@JuMP.constraint(m, ineq1, m[:x][1] >= 0)
32+
@JuMP.constraint(m, ineq2, m[:x][1]^2 + m[:x][2]^2 <= 0.5)
33+
@JuMP.NLconstraint(m, ineq3, sqrt(m[:x][3]) >= 0.1)
3434
return m
3535
end
3636

@@ -42,15 +42,27 @@ function test_flow_model_with_inequalities()
4242
end
4343

4444
function test_with_vector_constraint()
45-
m = jmp.Model()
46-
@jmp.variable(m, var[1:2])
47-
@jmp.constraint(m, con, var in moi.Nonnegatives(2))
45+
m = JuMP.Model()
46+
@JuMP.variable(m, var[1:2])
47+
@JuMP.constraint(m, con, var in moi.Nonnegatives(2))
4848
@test_throws(TypeError, eq_cons = get_equality_constraints(m))
4949
end
5050

51+
function test_with_fixed_variables()
52+
m = JuMP.Model()
53+
@JuMP.variable(m, x[1:2])
54+
# These bound constraints are picked up, then discarded as the
55+
# one-sided interval sets never "imply equality"
56+
@JuMP.variable(m, 0 <= y <= 1)
57+
JuMP.fix(x[1], 1.0)
58+
eq_cons = get_equality_constraints(m)
59+
@test length(eq_cons) == 1
60+
end
61+
5162
function runtests()
5263
test_flow_model_with_inequalities()
5364
test_with_vector_constraint()
65+
test_with_fixed_variables()
5466
end
5567

5668
end # module TestGetEquality

test/identify_variables.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,26 @@ function test_model_bad_constr_no_ineq()
179179
)
180180
end
181181

182+
function test_fixing_constraint()
183+
m = jmp.Model()
184+
@jmp.variable(m, x[1:2])
185+
jmp.fix(x[1], 1)
186+
fixing_con = jmp.FixRef(x[1])
187+
variables = identify_unique_variables(fixing_con)
188+
@test length(variables) == 1
189+
@test variables[1] === x[1]
190+
end
191+
192+
function test_inequality_with_bounds()
193+
m = jmp.Model()
194+
@jmp.variable(m, x[1:2])
195+
@jmp.variable(m, 0 <= y[1:2])
196+
@jmp.constraint(m, x[1] + 2*x[2]^2 == 1)
197+
variables = identify_unique_variables(m, include_inequality = true)
198+
pred_var_set = Set([x[1], x[2], y[1], y[2]])
199+
@test Set(variables) == pred_var_set
200+
end
201+
182202
function runtests()
183203
test_linear()
184204
test_quadratic()
@@ -191,6 +211,8 @@ function runtests()
191211
test_model_bad_constr()
192212
test_model_bad_constr_no_ineq()
193213
test_function_with_variable_squared()
214+
test_fixing_constraint()
215+
test_inequality_with_bounds()
194216
return
195217
end
196218

test/incidence_graph.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,25 @@ function test_get_incidence_graph_badconstraint()
9393
return
9494
end
9595

96+
function test_include_bound_as_inequality()
97+
m = jmp.Model()
98+
@jmp.variable(m, 0 <= x[1:2])
99+
@jmp.constraint(m, eq1, x[1] + 2*x[2] == 1)
100+
graph, con_node_map, var_node_map = get_bipartite_incidence_graph(
101+
m, include_inequality = true
102+
)
103+
A, B, E = graph
104+
@test length(A) == 3
105+
@test length(B) == 2
106+
@test length(E) == 4
107+
pred_con_set = Set([eq1, jmp.LowerBoundRef(x[1]), jmp.LowerBoundRef(x[2])])
108+
@test pred_con_set == keys(con_node_map)
109+
end
110+
96111
function runtests()
97112
test_get_incidence_graph()
98113
test_get_incidence_graph_badconstraint()
114+
test_include_bound_as_inequality()
99115
return
100116
end
101117

test/interface.jl

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
module TestInterface
2121

2222
using Test: @test, @test_throws
23-
import JuMP as jmp
23+
import JuMP
2424
import JuMPIn as ji
2525

2626
# Note: Do not import from IncidenceGraphInterface here; this will
@@ -80,7 +80,7 @@ end
8080

8181
function test_construct_interface_rectangular()
8282
m = make_degenerate_flow_model()
83-
@jmp.constraint(
83+
@JuMP.constraint(
8484
m,
8585
sum_flow_eqn,
8686
m[:flow] == sum(m[:flow_comp][:]),
@@ -165,8 +165,8 @@ function test_maximum_matching()
165165
matching = ji.maximum_matching(igraph)
166166
@test length(matching) == 7
167167
for (con, var) in matching
168-
@test typeof(con) <: jmp.ConstraintRef
169-
@test typeof(var) <: jmp.VariableRef
168+
@test typeof(con) <: JuMP.ConstraintRef
169+
@test typeof(var) <: JuMP.VariableRef
170170
@test var in Set(ji.get_adjacent(igraph, con))
171171
@test con in Set(ji.get_adjacent(igraph, var))
172172
end
@@ -225,6 +225,32 @@ function test_dulmage_mendelsohn()
225225
return nothing
226226
end
227227

228+
function test_overconstrained_due_to_fixed_variable()
229+
m = JuMP.Model()
230+
@JuMP.variable(m, x[1:2])
231+
@JuMP.constraint(m, x[1] + 2*x[2] == 1)
232+
@JuMP.constraint(m, 3*x[2] - x[2] == 0)
233+
JuMP.fix(x[1], 3)
234+
igraph = ji.IncidenceGraphInterface(m)
235+
con_dmp, var_dmp = ji.dulmage_mendelsohn(igraph)
236+
@test length(var_dmp.overconstrained) == 2
237+
@test length(con_dmp.overconstrained) == 2
238+
@test length(con_dmp.unmatched) == 1
239+
end
240+
241+
function test_overconstrained_due_to_including_bound()
242+
m = JuMP.Model()
243+
@JuMP.variable(m, x)
244+
@JuMP.variable(m, 0.01 <= y)
245+
@JuMP.constraint(m, 2*x + y == 1)
246+
@JuMP.NLconstraint(m, x == sqrt(y))
247+
igraph = ji.IncidenceGraphInterface(m, include_inequality = true)
248+
con_dmp, var_dmp = ji.dulmage_mendelsohn(igraph)
249+
@test length(var_dmp.overconstrained) == 2
250+
@test length(con_dmp.overconstrained) == 2
251+
@test length(con_dmp.unmatched) == 1
252+
end
253+
228254
function runtests()
229255
test_construct_interface()
230256
test_construct_interface_rectangular()
@@ -234,6 +260,8 @@ function runtests()
234260
test_get_adjacent_to_variable()
235261
test_maximum_matching()
236262
test_dulmage_mendelsohn()
263+
test_overconstrained_due_to_fixed_variable()
264+
test_overconstrained_due_to_including_bound()
237265
end
238266

239267
end

0 commit comments

Comments
 (0)