Skip to content

Commit d21c4e2

Browse files
committed
Enhance MBM structure to support per-row and per-bound M values in constraints
1 parent 0d66676 commit d21c4e2

3 files changed

Lines changed: 272 additions & 184 deletions

File tree

src/datatypes.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,15 +388,15 @@ end
388388

389389
mutable struct _MBM{O, T, M <: JuMP.AbstractModel} <: AbstractReformulationMethod
390390
optimizer::O
391-
M::Dict{LogicalVariableRef{M}, T}
392-
default_M::T
393-
conlvref::Vector{LogicalVariableRef{M}}
391+
M::Dict{LogicalVariableRef{M}, Union{T, Vector{T}}}
392+
default_M::T
393+
conlvref::Vector{LogicalVariableRef{M}}
394394

395395
function _MBM(method::MBM{O, T}, model::M) where {O, T, M <: JuMP.AbstractModel}
396396
new{O, T, M}(method.optimizer,
397-
Dict{LogicalVariableRef{M}, T}(),
397+
Dict{LogicalVariableRef{M}, Union{T, Vector{T}}}(),
398398
method.default_M,
399-
Vector{LogicalVariableRef{M}}()
399+
Vector{LogicalVariableRef{M}}()
400400
)
401401
end
402402
end

src/mbm.jl

Lines changed: 115 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -74,32 +74,30 @@ function reformulate_disjunct_constraint(
7474
return new_ref_cons
7575
end
7676

77+
# Per-row M values: method.M[d] is a Vector, index with [j] for row j
7778
function reformulate_disjunct_constraint(
7879
model::JuMP.AbstractModel,
7980
con::JuMP.VectorConstraint{T, S, R},
8081
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
8182
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
8283
method::_MBM
8384
) where {T, S <: _MOI.Nonpositives, R}
84-
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
85-
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
86-
con.func[i] - m_sum
85+
new_func = JuMP.@expression(model, [j=1:con.set.dimension],
86+
con.func[j] - sum(method.M[d][j] * bconref[d] for d in keys(method.M))
8787
)
8888
reform_con = JuMP.build_constraint(error, new_func, con.set)
8989
return [reform_con]
9090
end
9191

92-
9392
function reformulate_disjunct_constraint(
9493
model::JuMP.AbstractModel,
9594
con::JuMP.VectorConstraint{T, S, R},
9695
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
9796
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
9897
method::_MBM
9998
) where {T, S <: _MOI.Nonnegatives, R}
100-
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
101-
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
102-
con.func[i] + m_sum
99+
new_func = JuMP.@expression(model, [j=1:con.set.dimension],
100+
con.func[j] + sum(method.M[d][j] * bconref[d] for d in keys(method.M))
103101
)
104102
reform_con = JuMP.build_constraint(error, new_func, con.set)
105103
return [reform_con]
@@ -112,17 +110,16 @@ function reformulate_disjunct_constraint(
112110
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
113111
method::_MBM
114112
) where {T, S <: _MOI.Zeros, R}
115-
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
116-
upper_expr = JuMP.@expression(model, [i=1:con.set.dimension],
117-
con.func[i] + m_sum
113+
upper_expr = JuMP.@expression(model, [j=1:con.set.dimension],
114+
con.func[j] + sum(method.M[d][j] * bconref[d] for d in keys(method.M))
118115
)
119-
lower_expr = JuMP.@expression(model, [i=1:con.set.dimension],
120-
con.func[i] - m_sum
116+
lower_expr = JuMP.@expression(model, [j=1:con.set.dimension],
117+
con.func[j] - sum(method.M[d][j] * bconref[d] for d in keys(method.M))
121118
)
122-
upper_con = JuMP.build_constraint(error, upper_expr,
119+
upper_con = JuMP.build_constraint(error, upper_expr,
123120
MOI.Nonnegatives(con.set.dimension)
124121
)
125-
lower_con = JuMP.build_constraint(error, lower_expr,
122+
lower_con = JuMP.build_constraint(error, lower_expr,
126123
MOI.Nonpositives(con.set.dimension)
127124
)
128125
return [upper_con, lower_con]
@@ -156,25 +153,26 @@ function reformulate_disjunct_constraint(
156153
return [reform_con]
157154
end
158155

156+
# Per-bound M values: method.M[d] = [M_lower, M_upper]
159157
function reformulate_disjunct_constraint(
160158
model::JuMP.AbstractModel,
161159
con::JuMP.ScalarConstraint{T, S},
162160
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
163161
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
164162
method::_MBM
165163
) where {T, S <: _MOI.EqualTo}
166-
upper_func = JuMP.@expression(model,
167-
con.func - sum(method.M[i] * bconref[i] for i in keys(method.M))
168-
)
169-
lower_func = JuMP.@expression(model,
170-
con.func + sum(method.M[i] * bconref[i] for i in keys(method.M))
164+
lower_func = JuMP.@expression(model,
165+
con.func + sum(method.M[d][1] * bconref[d] for d in keys(method.M))
171166
)
172-
upper_con = JuMP.build_constraint(error, upper_func,
173-
MOI.LessThan(con.set.value)
167+
upper_func = JuMP.@expression(model,
168+
con.func - sum(method.M[d][2] * bconref[d] for d in keys(method.M))
174169
)
175-
lower_con = JuMP.build_constraint(error, lower_func,
170+
lower_con = JuMP.build_constraint(error, lower_func,
176171
MOI.GreaterThan(con.set.value)
177172
)
173+
upper_con = JuMP.build_constraint(error, upper_func,
174+
MOI.LessThan(con.set.value)
175+
)
178176
return [lower_con, upper_con]
179177
end
180178

@@ -185,21 +183,19 @@ function reformulate_disjunct_constraint(
185183
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
186184
method::_MBM
187185
) where {T, S <: _MOI.Interval}
188-
set_values = _set_values(con.set)
189-
upper_func = JuMP.@expression(model,
190-
con.func - sum(method.M[i] * bconref[i] for i in keys(method.M))
186+
set_values = _set_values(con.set)
187+
lower_func = JuMP.@expression(model,
188+
con.func + sum(method.M[d][1] * bconref[d] for d in keys(method.M))
191189
)
192-
upper_con = JuMP.build_constraint(error, upper_func,
193-
MOI.LessThan(set_values[2])
194-
)
195-
196-
lower_func = JuMP.@expression(model,
197-
con.func + sum(method.M[i] * bconref[i] for i in keys(method.M))
190+
upper_func = JuMP.@expression(model,
191+
con.func - sum(method.M[d][2] * bconref[d] for d in keys(method.M))
198192
)
199-
lower_con = JuMP.build_constraint(error, lower_func,
193+
lower_con = JuMP.build_constraint(error, lower_func,
200194
MOI.GreaterThan(set_values[1])
201195
)
202-
196+
upper_con = JuMP.build_constraint(error, upper_func,
197+
MOI.LessThan(set_values[2])
198+
)
203199
return [lower_con, upper_con]
204200
end
205201

@@ -219,65 +215,74 @@ end
219215
################################################################################
220216
# Dispatches over constraint types to reformulate into >= or <=
221217
# in order to solve the mini-model
218+
# Per-row M values for vector constraints: returns Vector{T}
222219
function _maximize_M(
223-
model::JuMP.AbstractModel,
224-
objective::JuMP.VectorConstraint{T, S, R},
225-
constraints::Vector{<:DisjunctConstraintRef},
220+
model::JuMP.AbstractModel,
221+
objective::JuMP.VectorConstraint{T, S, R},
222+
constraints::Vector{<:DisjunctConstraintRef},
226223
method::_MBM
227224
) where { T, S <: _MOI.Nonpositives, R}
228225
val_type = JuMP.value_type(typeof(model))
229-
return maximum(
226+
return [
230227
_maximize_M(
231-
model,
232-
JuMP.ScalarConstraint(objective.func[i], MOI.LessThan(zero(val_type))),
233-
constraints,
228+
model,
229+
JuMP.ScalarConstraint(objective.func[i], MOI.LessThan(zero(val_type))),
230+
constraints,
234231
method
235232
) for i in 1:objective.set.dimension
236-
)
233+
]
237234
end
238235

239236
function _maximize_M(
240-
model::JuMP.AbstractModel,
241-
objective::JuMP.VectorConstraint{T, S, R},
242-
constraints::Vector{<:DisjunctConstraintRef},
237+
model::JuMP.AbstractModel,
238+
objective::JuMP.VectorConstraint{T, S, R},
239+
constraints::Vector{<:DisjunctConstraintRef},
243240
method::_MBM
244241
) where { T, S <: _MOI.Nonnegatives, R}
245242
val_type = JuMP.value_type(typeof(model))
246-
return maximum(
243+
return [
247244
_maximize_M(
248-
model,
249-
JuMP.ScalarConstraint(objective.func[i], MOI.GreaterThan(zero(val_type))),
250-
constraints,
245+
model,
246+
JuMP.ScalarConstraint(
247+
objective.func[i],
248+
MOI.GreaterThan(zero(val_type))
249+
),
250+
constraints,
251251
method
252252
) for i in 1:objective.set.dimension
253-
)
253+
]
254254
end
255255

256+
# For Zeros, each row is an equality: return per-row M = max(M_lower, M_upper)
256257
function _maximize_M(
257-
model::JuMP.AbstractModel,
258-
objective::JuMP.VectorConstraint{T, S, R},
259-
constraints::Vector{<:DisjunctConstraintRef},
258+
model::JuMP.AbstractModel,
259+
objective::JuMP.VectorConstraint{T, S, R},
260+
constraints::Vector{<:DisjunctConstraintRef},
260261
method::_MBM
261262
) where { T, S <: _MOI.Zeros, R}
262263
val_type = JuMP.value_type(typeof(model))
263-
return max(
264-
maximum(
264+
return [
265+
max(
265266
_maximize_M(
266-
model,
267-
JuMP.ScalarConstraint(objective.func[i],MOI.GreaterThan(zero(val_type))),
268-
constraints,
267+
model,
268+
JuMP.ScalarConstraint(
269+
objective.func[i],
270+
MOI.GreaterThan(zero(val_type))
271+
),
272+
constraints,
269273
method
270-
) for i in 1:objective.set.dimension
271-
),
272-
maximum(
274+
),
273275
_maximize_M(
274-
model,
275-
JuMP.ScalarConstraint(objective.func[i], MOI.LessThan(zero(val_type))),
276-
constraints,
276+
model,
277+
JuMP.ScalarConstraint(
278+
objective.func[i],
279+
MOI.LessThan(zero(val_type))
280+
),
281+
constraints,
277282
method
278-
) for i in 1:objective.set.dimension
279-
)
280-
)
283+
)
284+
) for i in 1:objective.set.dimension
285+
]
281286
end
282287

283288
function _maximize_M(
@@ -289,56 +294,65 @@ function _maximize_M(
289294
return _mini_model(model, objective, constraints, method)
290295
end
291296

297+
# Per-bound M values for bidirectional constraints: returns [M_lower, M_upper]
292298
function _maximize_M(
293-
model::JuMP.AbstractModel,
294-
objective::JuMP.ScalarConstraint{T, S},
295-
constraints::Vector{<:DisjunctConstraintRef},
299+
model::JuMP.AbstractModel,
300+
objective::JuMP.ScalarConstraint{T, S},
301+
constraints::Vector{<:DisjunctConstraintRef},
296302
method::_MBM
297303
) where {T, S <: _MOI.EqualTo}
298304
set_value = objective.set.value
299-
return max(
300-
_mini_model(
301-
model,
302-
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_value)),
303-
constraints,
304-
method
305-
),
306-
_mini_model(
307-
model,
308-
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_value)),
309-
constraints,
310-
method
311-
)
305+
M_lower = _mini_model(
306+
model,
307+
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_value)),
308+
constraints,
309+
method
310+
)
311+
M_upper = _mini_model(
312+
model,
313+
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_value)),
314+
constraints,
315+
method
312316
)
317+
return [M_lower, M_upper]
313318
end
314319

315320
function _maximize_M(
316-
model::JuMP.AbstractModel,
317-
objective::JuMP.ScalarConstraint{T, S},
318-
constraints::Vector{<:DisjunctConstraintRef},
321+
model::JuMP.AbstractModel,
322+
objective::JuMP.ScalarConstraint{T, S},
323+
constraints::Vector{<:DisjunctConstraintRef},
319324
method::_MBM
320325
) where {T, S <: _MOI.Interval}
321326
set_values = _set_values(objective.set) # Returns (lower, upper)
322-
return max(
323-
_mini_model(
324-
model,
325-
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_values[1])),
326-
constraints,
327-
method
328-
),
329-
_mini_model(
330-
model,
331-
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_values[2])),
332-
constraints,
333-
method
334-
)
327+
M_lower = _mini_model(
328+
model,
329+
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_values[1])),
330+
constraints,
331+
method
335332
)
333+
M_upper = _mini_model(
334+
model,
335+
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_values[2])),
336+
constraints,
337+
method
338+
)
339+
return [M_lower, M_upper]
336340
end
337341

342+
# Nested Disjunctions: M computation skipped, handler creates fresh MBM
338343
function _maximize_M(
339-
::JuMP.AbstractModel,
340-
::F,
341-
::Vector{<:DisjunctConstraintRef},
344+
::JuMP.AbstractModel,
345+
::Disjunction,
346+
::Vector{<:DisjunctConstraintRef},
347+
::_MBM
348+
)
349+
return nothing
350+
end
351+
352+
function _maximize_M(
353+
::JuMP.AbstractModel,
354+
::F,
355+
::Vector{<:DisjunctConstraintRef},
342356
::_MBM
343357
) where {F}
344358
error("This type of constraints and objective constraint has " *

0 commit comments

Comments
 (0)