Skip to content

Commit 0044ea4

Browse files
committed
Updated documentation, JuMP. calls.
1 parent 0ae3623 commit 0044ea4

File tree

4 files changed

+122
-127
lines changed

4 files changed

+122
-127
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ The following reformulation methods are currently supported:
172172

173173
3. [Indicator](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints): This method reformulates each disjunct constraint into an indicator constraint with the Boolean reformulation counterpart of the Logical variable used to define the disjunct constraint.
174174

175+
4. [MBM](https://doi.org/10.1016/j.compchemeng.2015.02.013): The multiple big-m method creates multiple M values for each disjunct constraint. The 'MBM' struct is created with the following required argument:
176+
177+
- `optimizer`: Optimizer to use when solving subproblems to determine M values. This is a required value.
178+
175179
## Release Notes
176180

177181
Prior to `v0.4.0`, the package did not leverage the JuMP extension capabilities and was not as robust. For these earlier releases, refer to [Perez, Joshi, and Grossmann, 2023](https://arxiv.org/abs/2304.10492v1) and the following [JuliaCon 2022 Talk](https://www.youtube.com/watch?v=AMIrgTTfUkI).

src/datatypes.jl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -368,21 +368,19 @@ struct BigM{T} <: AbstractReformulationMethod
368368
end
369369

370370
"""
371-
MBM{T} <: AbstractReformulationMethod
371+
MBM{O} <: AbstractReformulationMethod
372372
373373
A type for using the multiple big-M reformulation approach for disjunctive constraints.
374374
375375
**Fields**
376376
- 'optimizer::O': Optimizer to use when solving mini-models (required).
377-
- `value::T`: MBM value (default = `1e9`).
378377
"""
379-
struct MBM{T,O} <: AbstractReformulationMethod
380-
value::T
378+
struct MBM{O} <: AbstractReformulationMethod
381379
optimizer::O
382380

383-
# Constructor with optimizer (required) and val (optional)
384-
function MBM(optimizer::O, val::T = 1e9) where {T,O}
385-
new{T,O}(val, optimizer)
381+
# Constructor with optimizer (required)
382+
function MBM(optimizer::O) where {O}
383+
new{O}(optimizer)
386384
end
387385
end
388386

src/mbm.jl

Lines changed: 90 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ function reformulate_disjunction(model::JuMP.AbstractModel,
77
method::MBM
88
)
99
ref_cons = Vector{JuMP.AbstractConstraint}()
10+
for d in disj.indicators
11+
constraints = copy(_indicator_to_constraints(model)[d]) # Make a copy to avoid modifying during iteration
12+
for cref in constraints
13+
con = JuMP.constraint_object(cref)
14+
if con isa JuMP.ScalarConstraint{<:Any, <:MOI.Interval}
15+
# Create lower and upper bound constraints
16+
lower_con = JuMP.build_constraint(error, con.func, MOI.GreaterThan(con.set.lower))
17+
upper_con = JuMP.build_constraint(error, con.func, MOI.LessThan(con.set.upper))
18+
# Create new disjunct constraints
19+
JuMP.add_constraint(model, _DisjunctConstraint(lower_con, d))
20+
JuMP.add_constraint(model, _DisjunctConstraint(upper_con, d))
21+
JuMP.delete(model, cref)
22+
end
23+
end
24+
end
1025
for d in disj.indicators
1126
_reformulate_disjunct(model, ref_cons, d, LogicalVariableRef[x for x in disj.indicators if x != d], method)
1227
end
@@ -21,29 +36,33 @@ function _reformulate_disjunct(
2136
conlvref::Vector{LogicalVariableRef},
2237
method::AbstractReformulationMethod
2338
)
24-
bconref = VariableRef[binary_variable(i) for i in conlvref]
39+
40+
bconref = Dict(d => binary_variable(d) for d in conlvref)
2541
!haskey(_indicator_to_constraints(model), lvref) && return
26-
M = Vector{Float64}(undef, length(conlvref))
27-
for (i,d) in enumerate(conlvref)
28-
M[i] = maximum(_maximize_M(model, constraint_object(cref),
29-
Vector{DisjunctConstraintRef}(_indicator_to_constraints(model)[d]), method)
30-
for cref in _indicator_to_constraints(model)[lvref])
42+
constraints = _indicator_to_constraints(model)[lvref]
43+
M = Dict{LogicalVariableRef,Float64}()
44+
sizehint!(M, length(conlvref))
45+
for d in conlvref
46+
M[d] = maximum(_maximize_M(model, JuMP.constraint_object(cref),
47+
Vector{DisjunctConstraintRef}(_indicator_to_constraints(model)[d]), method) for cref in constraints)
3148
end
32-
for cref in _indicator_to_constraints(model)[lvref]
49+
50+
isempty(constraints) || sizehint!(ref_cons, length(constraints))
51+
for cref in constraints
3352
con = JuMP.constraint_object(cref)
34-
append!(ref_cons, reformulate_disjunct_constraint(model, con, bconref, M, method))
53+
append!(ref_cons, reformulate_disjunct_constraint(model, con, Dict{LogicalVariableRef,VariableRef}(bconref), M, method))
3554
end
3655
return ref_cons
3756
end
3857

3958
function reformulate_disjunct_constraint(
4059
model::JuMP.AbstractModel,
4160
con::JuMP.VectorConstraint{T, S, R},
42-
bconref:: Vector{VariableRef},
43-
M::Vector{Float64},
61+
bconref:: Dict{LogicalVariableRef,VariableRef},
62+
M::Dict{LogicalVariableRef,Float64},
4463
method::MBM
4564
) where {T, S <: _MOI.Nonpositives, R}
46-
m_sum = sum(M[i] * bconref[i] for i in 1:length(M))
65+
m_sum = sum(M[i] * bconref[i] for i in keys(M))
4766
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
4867
con.func[i] - m_sum
4968
)
@@ -54,11 +73,11 @@ end
5473
function reformulate_disjunct_constraint(
5574
model::JuMP.AbstractModel,
5675
con::JuMP.VectorConstraint{T, S, R},
57-
bconref:: Vector{VariableRef},
58-
M::Vector{Float64},
76+
bconref:: Dict{LogicalVariableRef,VariableRef},
77+
M::Dict{LogicalVariableRef,Float64},
5978
method::MBM
6079
) where {T, S <: _MOI.Nonnegatives, R}
61-
m_sum = sum(M[i] * bconref[i] for i in 1:length(M))
80+
m_sum = sum(M[i] * bconref[i] for i in keys(M))
6281
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
6382
con.func[i] + m_sum
6483
)
@@ -69,11 +88,11 @@ end
6988
function reformulate_disjunct_constraint(
7089
model::JuMP.AbstractModel,
7190
con::JuMP.VectorConstraint{T, S, R},
72-
bconref:: Vector{VariableRef},
73-
M::Vector{Float64},
91+
bconref:: Dict{LogicalVariableRef,VariableRef},
92+
M::Dict{LogicalVariableRef,Float64},
7493
method::MBM
7594
) where {T, S <: _MOI.Zeros, R}
76-
m_sum = sum(M[i] * bconref[i] for i in 1:length(M))
95+
m_sum = sum(M[i] * bconref[i] for i in keys(M))
7796
upper_expr = JuMP.@expression(model, [i=1:con.set.dimension],
7897
con.func[i] + m_sum
7998
)
@@ -89,55 +108,49 @@ end
89108
function reformulate_disjunct_constraint(
90109
model::JuMP.AbstractModel,
91110
con::JuMP.ScalarConstraint{T, S},
92-
bconref:: Vector{VariableRef},
93-
M::Vector{Float64},
111+
bconref:: Dict{LogicalVariableRef,VariableRef},
112+
M::Dict{LogicalVariableRef,Float64},
94113
method::MBM
95114
) where {T, S <: _MOI.LessThan}
96-
new_func = JuMP.@expression(model, con.func - sum(M[i] * bconref[i] for i in 1:length(M)))
115+
new_func = JuMP.@expression(model, con.func - sum(M[i] * bconref[i] for i in keys(M)))
97116
reform_con = JuMP.build_constraint(error, new_func, con.set)
98117
return [reform_con]
99118
end
100119

101120
function reformulate_disjunct_constraint(
102121
model::JuMP.AbstractModel,
103122
con::JuMP.ScalarConstraint{T, S},
104-
bconref:: Vector{VariableRef},
105-
M::Vector{Float64},
123+
bconref:: Dict{LogicalVariableRef,VariableRef},
124+
M::Dict{LogicalVariableRef,Float64},
106125
method::MBM
107126
) where {T, S <: _MOI.GreaterThan}
108-
new_func = JuMP.@expression(model, con.func + sum(M[i] * bconref[i] for i in 1:length(M)))
127+
new_func = JuMP.@expression(model, con.func + sum(M[i] * bconref[i] for i in keys(M)))
109128
reform_con = JuMP.build_constraint(error, new_func, con.set)
110129
return [reform_con]
111130
end
112131

113132
function reformulate_disjunct_constraint(
114133
model::JuMP.AbstractModel,
115134
con::JuMP.ScalarConstraint{T, S},
116-
bconref::Vector{VariableRef},
117-
M::Vector{Float64},
135+
bconref:: Dict{LogicalVariableRef,VariableRef},
136+
M::Dict{LogicalVariableRef,Float64},
118137
method::MBM
119-
) where {T, S <: _MOI.Interval}
120-
lower_func = JuMP.@expression(model, con.func - sum(M[i] * bconref[i] for i in 1:length(M)))
121-
upper_func = JuMP.@expression(model, con.func + sum(M[i] * bconref[i] for i in 1:length(M)))
138+
) where {T, S <: _MOI.EqualTo}
139+
upper_func = JuMP.@expression(model, con.func - sum(M[i] * bconref[i] for i in keys(M)))
140+
lower_func = JuMP.@expression(model, con.func + sum(M[i] * bconref[i] for i in keys(M)))
122141

123-
lower_con = JuMP.build_constraint(error, lower_func, MOI.GreaterThan(con.set.lower))
124-
upper_con = JuMP.build_constraint(error, upper_func, MOI.LessThan(con.set.upper))
142+
upper_con = JuMP.build_constraint(error, upper_func, MOI.LessThan(con.set.value))
143+
lower_con = JuMP.build_constraint(error, lower_func, MOI.GreaterThan(con.set.value))
125144
return [lower_con, upper_con]
126145
end
127-
128146
function reformulate_disjunct_constraint(
129147
model::JuMP.AbstractModel,
130-
con::JuMP.ScalarConstraint{T, S},
131-
bconref::Vector{VariableRef},
132-
M::Vector{Float64},
148+
con::Any,
149+
bconref:: Dict{LogicalVariableRef,VariableRef},
150+
M::Dict{LogicalVariableRef,Float64},
133151
method::MBM
134-
) where {T, S <: _MOI.EqualTo}
135-
upper_func = JuMP.@expression(model, con.func - sum(M[i] * bconref[i] for i in 1:length(M)))
136-
lower_func = JuMP.@expression(model, con.func + sum(M[i] * bconref[i] for i in 1:length(M)))
137-
138-
upper_con = JuMP.build_constraint(error, upper_func, MOI.LessThan(con.set.value))
139-
lower_con = JuMP.build_constraint(error, lower_func, MOI.GreaterThan(con.set.value))
140-
return [lower_con, upper_con]
152+
)
153+
error("Constraint type $(typeof(con)) is not supported by the Multiple Big-M reformulation method.")
141154
end
142155

143156
################################################################################
@@ -183,16 +196,6 @@ function _maximize_M(
183196
return _mini_model(model, objective, constraints, method)
184197
end
185198

186-
function _maximize_M(
187-
model::JuMP.AbstractModel,
188-
objective::ScalarConstraint{T, S},
189-
constraints::Vector{DisjunctConstraintRef},
190-
method::MBM
191-
) where {T, S <: _MOI.Interval}
192-
return max(_mini_model(model, ScalarConstraint(objective.func, MOI.GreaterThan(objective.set.lower)), constraints, method),
193-
_mini_model(model, ScalarConstraint(objective.func, MOI.LessThan(objective.set.upper)), constraints, method))
194-
end
195-
196199
function _maximize_M(
197200
model::JuMP.AbstractModel,
198201
objective::ScalarConstraint{T, S},
@@ -221,38 +224,38 @@ function _mini_model(
221224
constraints::Vector{DisjunctConstraintRef},
222225
method::MBM
223226
) where {T,S <: Union{_MOI.LessThan, _MOI.GreaterThan}}
224-
sub_model = Model()
225-
new_vars = Dict{VariableRef, VariableRef}()
226-
for var in all_variables(model)
227-
new_vars[var] = @variable(sub_model, base_name= "sub_model_$(JuMP.name(var))")
228-
if is_fixed(var)
229-
JuMP.fix(new_vars[var], fix_value(var); force=true)
227+
sub_model = JuMP.Model()
228+
new_vars = Dict{JuMP.VariableRef, JuMP.VariableRef}()
229+
for var in JuMP.all_variables(model)
230+
new_vars[var] = JuMP.@variable(sub_model, base_name= "sub_model_$(JuMP.name(var))")
231+
if JuMP.is_fixed(var)
232+
JuMP.fix(new_vars[var], JuMP.fix_value(var); force=true)
230233
end
231-
if is_integer(var)
234+
if JuMP.is_integer(var)
232235
JuMP.set_integer(new_vars[var])
233236
end
234-
if has_upper_bound(var)
235-
set_upper_bound(new_vars[var], upper_bound(var))
237+
if JuMP.has_upper_bound(var)
238+
JuMP.set_upper_bound(new_vars[var], JuMP.upper_bound(var))
236239
end
237-
if has_lower_bound(var)
238-
set_lower_bound(new_vars[var], lower_bound(var))
240+
if JuMP.has_lower_bound(var)
241+
JuMP.set_lower_bound(new_vars[var], JuMP.lower_bound(var))
239242
end
240-
if has_start_value(var)
241-
JuMP.set_start_value(new_vars[var], start_value(var))
243+
if JuMP.has_start_value(var)
244+
JuMP.set_start_value(new_vars[var], JuMP.start_value(var))
242245
end
243246
end
244-
for con in [constraint_object(con) for con in constraints]
247+
for con in [JuMP.constraint_object(con) for con in constraints]
245248
expr = replace_variables_in_constraint(con.func, new_vars)
246-
@constraint(sub_model, expr * 1.0 in con.set)
249+
JuMP.@constraint(sub_model, expr * 1.0 in con.set)
247250
end
248251
constraint_to_objective(sub_model, objective, new_vars)
249-
set_optimizer(sub_model, method.optimizer)
250-
set_silent(sub_model)
251-
optimize!(sub_model)
252+
JuMP.set_optimizer(sub_model, method.optimizer)
253+
JuMP.set_silent(sub_model)
254+
JuMP.optimize!(sub_model)
252255
if JuMP.termination_status(sub_model) != MOI.OPTIMAL || !JuMP.has_values(sub_model) || JuMP.primal_status(sub_model) != MOI.FEASIBLE_POINT
253-
M = 200
256+
M = 1e9
254257
else
255-
M = objective_value(sub_model)
258+
M = JuMP.objective_value(sub_model)
256259
end
257260
return M
258261
end
@@ -270,14 +273,14 @@ end
270273
################################################################################
271274
# CONSTRAINT TO OBJECTIVE
272275
################################################################################
273-
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::ScalarConstraint{<:AbstractJuMPScalar, MOI.LessThan{Float64}}, new_vars::Dict{VariableRef, VariableRef})
274-
@objective(sub_model, Max, - obj.set.upper + replace_variables_in_constraint(obj.func, new_vars))
276+
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::JuMP.ScalarConstraint{<:JuMP.AbstractJuMPScalar, MOI.LessThan{Float64}}, new_vars::Dict{JuMP.VariableRef, JuMP.VariableRef})
277+
JuMP.@objective(sub_model, Max, - obj.set.upper + replace_variables_in_constraint(obj.func, new_vars))
275278
end
276-
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::ScalarConstraint{<:AbstractJuMPScalar, MOI.GreaterThan{Float64}}, new_vars::Dict{VariableRef, VariableRef})
277-
@objective(sub_model, Max, - replace_variables_in_constraint(obj.func, new_vars) + obj.set.lower)
279+
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::JuMP.ScalarConstraint{<:JuMP.AbstractJuMPScalar, MOI.GreaterThan{Float64}}, new_vars::Dict{JuMP.VariableRef, JuMP.VariableRef})
280+
JuMP.@objective(sub_model, Max, - replace_variables_in_constraint(obj.func, new_vars) + obj.set.lower)
278281
end
279282

280-
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::ScalarConstraint, new_vars::Dict{VariableRef, VariableRef})
283+
function constraint_to_objective(sub_model::JuMP.AbstractModel,obj::JuMP.ScalarConstraint, new_vars::Dict{JuMP.VariableRef, JuMP.VariableRef})
281284
error("This type of constraint is not supported, only greater than and less than constraints are supported with intervals and equalities being converted.")
282285
end
283286

@@ -287,40 +290,40 @@ end
287290

288291

289292

290-
function replace_variables_in_constraint(fun::GenericVariableRef, var_map::Dict{VariableRef, VariableRef})
293+
function replace_variables_in_constraint(fun::JuMP.GenericVariableRef, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef})
291294
return var_map[fun]
292295
end
293296

294-
function replace_variables_in_constraint(fun::AffExpr, var_map::Dict{VariableRef, VariableRef})
295-
new_aff = zero(AffExpr)
297+
function replace_variables_in_constraint(fun::JuMP.AffExpr, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef})
298+
new_aff = JuMP.zero(JuMP.AffExpr)
296299
for (var, coef) in fun.terms
297300
new_var = var_map[var]
298-
add_to_expression!(new_aff, coef, new_var)
301+
JuMP.add_to_expression!(new_aff, coef, new_var)
299302
end
300303
new_aff.constant = fun.constant
301304
return new_aff
302305
end
303306

304-
function replace_variables_in_constraint(fun::QuadExpr, var_map::Dict{VariableRef, VariableRef})
305-
new_quad = zero(QuadExpr)
307+
function replace_variables_in_constraint(fun::JuMP.QuadExpr, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef})
308+
new_quad = JuMP.zero(JuMP.QuadExpr)
306309
for (vars, coef) in fun.terms
307-
add_to_expression!(new_quad, coef, var_map[vars.a], var_map[vars.b])
310+
JuMP.add_to_expression!(new_quad, coef, var_map[vars.a], var_map[vars.b])
308311
end
309312
new_aff = replace_variables_in_constraint(fun.aff, var_map)
310-
add_to_expression!(new_quad, new_aff)
313+
JuMP.add_to_expression!(new_quad, new_aff)
311314
return new_quad
312315
end
313316

314-
function replace_variables_in_constraint(fun::Number, var_map::Dict{VariableRef, VariableRef})
317+
function replace_variables_in_constraint(fun::Number, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef})
315318
return fun
316319
end
317320

318-
function replace_variables_in_constraint(fun::NonlinearExpr, var_map::Dict{VariableRef, VariableRef})
321+
function replace_variables_in_constraint(fun::JuMP.NonlinearExpr, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef})
319322
new_args = Any[replace_variables_in_constraint(arg, var_map) for arg in fun.args]
320323
return JuMP.NonlinearExpr(fun.head, new_args)
321324
end
322325

323-
function replace_variables_in_constraint(fun::Vector{T}, var_map::Dict{VariableRef, VariableRef}) where T
326+
function replace_variables_in_constraint(fun::Vector{T}, var_map::Dict{JuMP.VariableRef, JuMP.VariableRef}) where T
324327
return [replace_variables_in_constraint(expr, var_map) for expr in fun]
325328
end
326329

0 commit comments

Comments
 (0)