@@ -4,32 +4,40 @@ function reformulate(m, disj, bin_var, reformulation, param)
4
4
for (i,constr) in enumerate (disj)
5
5
if constr isa Vector || constr isa Tuple
6
6
for (j,constr_j) in enumerate (constr)
7
- init_reformulation (m, constr_j, bin_var, reformulation, param, i, j)
7
+ apply_reformulation (m, constr_j, bin_var, reformulation, param, i, j)
8
8
end
9
9
elseif constr isa ConstraintRef || typeof (constr) <: Array || constr isa JuMP. Containers. DenseAxisArray
10
- init_reformulation (m, constr, bin_var, reformulation, param, i)
10
+ apply_reformulation (m, constr, bin_var, reformulation, param, i)
11
11
end
12
12
end
13
13
if reformulation == :CHR
14
14
add_disaggregated_constr (m, disj, vars)
15
15
end
16
16
end
17
17
18
- function init_reformulation (m, constr, bin_var, reformulation, param, i, j = missing )
18
+ function apply_reformulation (m, constr, bin_var, reformulation, param, i, j = missing )
19
19
param = get_reform_param (param, i, j) # M or eps
20
20
if constr isa ConstraintRef
21
- eval (:( $ reformulation ( $ m, $ constr, $ bin_var, $ i, $ j, missing , $ param)) )
21
+ call_reformulation ( reformulation, m, constr, bin_var, i, missing , param)
22
22
elseif typeof (constr) <: Array
23
23
for k in Iterators. product ([1 : s for s in size (constr)]. .. )
24
- eval (:( $ reformulation ( $ m, $ constr, $ bin_var, $ i, $ j, $ k, $ param)) )
24
+ call_reformulation ( reformulation, m, constr, bin_var, i, k, param)
25
25
end
26
26
elseif constr isa JuMP. Containers. DenseAxisArray
27
27
for k in Iterators. product ([s for s in constr. axes]. .. )
28
- eval (:( $ reformulation ( $ m, $ constr, $ M, $ bin_var, $ i, $ j, $ k, $ param)) )
28
+ call_reformulation ( reformulation, m, constr, bin_var, i, k, param)
29
29
end
30
30
end
31
31
end
32
32
33
+ function call_reformulation (reformulation, m, constr, bin_var, i, k, param)
34
+ if reformulation == :BMR
35
+ BMR! (m, constr, bin_var, i, k, param)
36
+ elseif reformulation == :CHR
37
+ CHR! (m, constr, bin_var, i, k, param)
38
+ end
39
+ end
40
+
33
41
function get_reform_param (param, i, j)
34
42
if param isa Number || ismissing (param)
35
43
param = param
@@ -48,7 +56,7 @@ function get_reform_param(param, i, j)
48
56
end
49
57
end
50
58
51
- function BMR (m, constr, bin_var, i, j , k, M)
59
+ function BMR! (m, constr, bin_var, i, k, M)
52
60
if ismissing (k)
53
61
@assert is_valid (m,constr) " $constr is not a valid constraint in the model."
54
62
ref = constr
@@ -58,14 +66,13 @@ function BMR(m, constr, bin_var, i, j, k, M)
58
66
end
59
67
if ismissing (M)
60
68
M = apply_interval_arithmetic (ref)
61
- @warn " No M value passed for $ref . M = $M was inferred from the variable bounds."
69
+ # @warn "No M value passed for $ref. M = $M was inferred from the variable bounds."
62
70
end
71
+ bin_var_ref = variable_by_name (ref. model, " $bin_var [$i ]" )
63
72
if ref isa NonlinearConstraintRef
64
- # Add code for Issue #20
65
- else
66
- add_to_function_constant (ref, - M)
67
- bin_var_ref = variable_by_name (ref. model, string (bin_var[i]))
68
- set_normalized_coefficient (ref, bin_var_ref , M)
73
+ nl_bigM (ref, bin_var_ref, M)
74
+ elseif ref isa ConstraintRef
75
+ lin_bigM (ref, bin_var_ref, M)
69
76
end
70
77
end
71
78
@@ -108,7 +115,34 @@ function apply_interval_arithmetic(ref)
108
115
end
109
116
end
110
117
111
- function CHR (m, constr, bin_var, i, j, k, eps)
118
+ function lin_bigM (ref, bin_var_ref, M)
119
+ add_to_function_constant (ref, - M)
120
+ set_normalized_coefficient (ref, bin_var_ref , M)
121
+ end
122
+
123
+ function nl_bigM (ref, bin_var_ref, M)
124
+ # extract info
125
+ vars = ref. model[:original_model_variables ]
126
+
127
+ # create symbolic variables (using Symbolics.jl)
128
+ sym_vars = []
129
+ for var in vars
130
+ var_sym = Symbol (var)
131
+ push! (sym_vars, eval (:(Symbolics. @variables ($ var_sym)))[1 ])
132
+ end
133
+ bin_var_sym = Symbol (bin_var_ref)
134
+ sym_bin_var = eval (:(Symbolics. @variables ($ bin_var_sym)))[1 ]
135
+
136
+ # parse ref
137
+ op, lhs, rhs = parse_NLconstraint (ref)
138
+ gx = eval (lhs) # convert the LHS of the constraint into a Symbolic expression
139
+ gx = gx - M* (1 - sym_bin_var) # add bigM
140
+
141
+ # update constraint
142
+ replace_NLconstraint (ref, gx, op, rhs)
143
+ end
144
+
145
+ function CHR! (m, constr, bin_var, i, k, eps)
112
146
if ismissing (k)
113
147
if constr isa NonlinearConstraintRef # NOTE: CAN'T CHECK IF NL CONSTR IS VALID
114
148
# @assert constr in keys(object_dictionary(m)) "$constr is not a named reference in the model."
@@ -124,7 +158,7 @@ function CHR(m, constr, bin_var, i, j, k, eps)
124
158
end
125
159
ref = constr[k... ]
126
160
end
127
- bin_var_ref = variable_by_name (ref. model, string ( bin_var[i]) )
161
+ bin_var_ref = variable_by_name (ref. model, " $ bin_var [$i ] " )
128
162
for var in m[:original_model_variables ]
129
163
# get bounds for disaggregated variable
130
164
@assert has_upper_bound (var) || is_binary (var) " Variable $var does not have an upper bound."
@@ -141,14 +175,14 @@ function CHR(m, constr, bin_var, i, j, k, eps)
141
175
end
142
176
end
143
177
# create convex hull constraint
144
- if ref isa NonlinearConstraintRef
145
- nl_perspective_function (ref, bin_var_ref, i, j, k, eps)
178
+ if ref isa NonlinearConstraintRef || constraint_object (ref) . func isa QuadExpr
179
+ nl_perspective_function (ref, bin_var_ref, i, eps)
146
180
elseif ref isa ConstraintRef
147
- lin_perspective_function (ref, bin_var_ref, i, j, k, eps )
181
+ lin_perspective_function (ref, bin_var_ref, i)
148
182
end
149
183
end
150
184
151
- function lin_perspective_function (ref, bin_var_ref, i, j, k, eps )
185
+ function lin_perspective_function (ref, bin_var_ref, i)
152
186
# check constraint type
153
187
ref_obj = constraint_object (ref)
154
188
@assert ref_obj. set isa MOI. LessThan || ref_obj. set isa MOI. GreaterThan || ref_obj. set isa MOI. EqualTo " $ref must be one the following: GreaterThan, LessThan, or EqualTo."
@@ -166,42 +200,32 @@ function lin_perspective_function(ref, bin_var_ref, i, j, k, eps)
166
200
end
167
201
end
168
202
169
- function nl_perspective_function (ref, bin_var_ref, i, j, k, eps)
203
+ function nl_perspective_function (ref, bin_var_ref, i, eps)
170
204
# extract info
171
- m = ref. model
172
- disj_name = replace (" $(bin_var_ref) " , " _binary" => " " )
173
- vars = m[:original_model_variables ]
174
- j = ismissing (j) ? " " : " _$j "
175
- k = ismissing (k) ? " " : " _$k "
205
+ vars = ref. model[:original_model_variables ]
176
206
177
- # check function has a single comparrison operator (<=, >=, ==)
178
- ref_str = string (ref)
179
- @assert length (findall (r" [<>]" , ref_str)) <= 1 " $ref must be one of the following: GreaterThan, LessThan, or EqualTo."
180
-
181
- # create symbolic variables (using Symbolics.jl v0.1.25)
207
+ # create symbolic variables (using Symbolics.jl)
182
208
sym_vars = []
209
+ sym_i_vars = []
183
210
for var in vars
184
- var_sym = Symbol (var)
211
+ var_sym = Symbol (var) # original variable
185
212
push! (sym_vars, eval (:(Symbolics. @variables ($ var_sym)))[1 ])
213
+ var_i_sym = Symbol (" $(var) _$i " ) # disaggregated variable
214
+ push! (sym_i_vars, eval (:(Symbolics. @variables ($ var_i_sym)))[1 ])
186
215
end
187
216
ϵ = eps # epsilon parameter for perspective function (See Furman, Sawaya, Grossmann [2020] perspecive function)
188
217
λ = Num (Symbolics. Sym {Float64} (Symbol (bin_var_ref)))
189
218
FSG1 = Num (Symbolics. Sym {Float64} (gensym ())) # this will become: [(1-ϵ)⋅λ + ϵ] (See Furman, Sawaya, Grossmann [2020] perspecive function)
190
219
FSG2 = Num (Symbolics. Sym {Float64} (gensym ())) # this will become: ϵ⋅(1-λ) (See Furman, Sawaya, Grossmann [2020] perspecive function)
191
220
192
- # convert ref_str into an Expr and extract comparrison operator (<=, >=, ==),
193
- # constraint function, and RHS
194
- ref_sym = Meta. parse (ref_str)
195
- ref_expr = ref_sym. args
196
- op = eval (ref_expr[1 ]) # comparrison operator
197
- @assert op in [>= , <= , == ] " An invalid operator is used in the constraint expression."
198
- rhs = ref_expr[3 ] # RHS of constraint
199
- gx = eval (ref_expr[2 ]) # convert the LHS of the constraint into a Symbolic function
221
+ # parse ref
222
+ op, lhs, rhs = parse_NLconstraint (ref)
223
+ gx = eval (lhs) # convert the LHS of the constraint into a Symbolic expression
200
224
201
225
# use symbolic substitution to obtain the following expression:
202
226
# [(1-ϵ)⋅λ + ϵ]⋅g(v/[(1-ϵ)⋅λ + ϵ]) - ϵ⋅g(0)⋅(1-λ) <= 0
203
227
# first term
204
- g1 = FSG1* substitute (gx, Dict (var => var / FSG1 for var in sym_vars))
228
+ g1 = FSG1* substitute (gx, Dict (var => var_i / FSG1 for ( var,var_i) in zip ( sym_vars,sym_i_vars) ))
205
229
# second term
206
230
g2 = FSG2* substitute (gx, Dict (var => 0 for var in sym_vars))
207
231
# create perspective function and simplify
@@ -211,26 +235,62 @@ function nl_perspective_function(ref, bin_var_ref, i, j, k, eps)
211
235
FSG2 => ϵ* (1 - λ)))
212
236
pers_func = simplify (pers_func)
213
237
214
- # convert pers_func to Expr
215
- # use build_function from Symbolics.jl to convert pers_func into an Expr
238
+ replace_NLconstraint (ref, pers_func, op, rhs)
239
+
240
+ # NOTE: the new NLconstraint cannot be assigned a name (not an option in add_NL_constraint)
241
+ # disj_name = replace("$(bin_var_ref)", "_binary" => "")
242
+ # j = ismissing(j) ? "" : "_$j"
243
+ # k = ismissing(k) ? "" : "_$k"
244
+ # pers_func_name = Symbol("perspective_func_$(disj_name)$(j)$(k)")
245
+ end
246
+
247
+ function parse_NLconstraint (ref)
248
+ # check function has a single comparrison operator (<=, >=, ==)
249
+ ref_str = string (ref)
250
+ # ref_str = replace(ref_str," = " => " == ") #replace = for == in NLconstraint (for older versions of JuMP)
251
+ ref_str = split (ref_str," : " )[end ] # remove name if Quadratic Constraint has a name
252
+
253
+ # convert ref_str into an Expr and extract comparrison operator (<=, >=, ==),
254
+ # constraint function, and RHS
255
+ ref_expr = Meta. parse (ref_str). args
256
+ op = eval (ref_expr[1 ]) # comparrison operator
257
+ @assert op in [>= , <= , == ] " $ref must be one of the following: GreaterThan, LessThan, or EqualTo."
258
+ lhs = ref_expr[2 ] # LHS of the constraint
259
+ rhs = ref_expr[3 ] # RHS of constraint
260
+
261
+ return op, lhs, rhs
262
+ end
263
+
264
+ function replace_NLconstraint (ref, sym_expr, op, rhs)
265
+ # convert sym_expr to Expr
266
+ # use build_function from Symbolics.jl to convert sym_expr into an Expr
216
267
# where any operators (i.e. exp, *, <=) are replaced by the actual
217
268
# operator (not the symbol).
218
269
# This is done to later replace the symbolic variables with JuMP variables,
219
270
# without messing with the math operators.
220
- pers_func_expr = Base. remove_linenums! (build_function (pers_func)). args[2 ]. args[1 ]
271
+ if ref isa NonlinearConstraintRef
272
+ expr = Base. remove_linenums! (build_function (sym_expr)). args[2 ]. args[1 ]
273
+ elseif ref isa ConstraintRef
274
+ expr = Base. remove_linenums! (build_function (op (sym_expr,rhs))). args[2 ]. args[1 ]
275
+ end
221
276
222
277
# replace symbolic variables by their JuMP variables
223
- replace_JuMPvars! (pers_func_expr, m)
278
+ m = ref. model
279
+ replace_JuMPvars! (expr, m)
224
280
# replace the math operators by symbols
225
- replace_operators! (pers_func_expr)
226
- # determine bounds of original constraint (note: if op is ==, both bounds are set to rhs)
227
- upper_b = (op == >= ) ? Inf : rhs
228
- lower_b = (op == <= ) ? - Inf : rhs
229
- # replace NL constraint currently in the model with the reformulated one
230
- m. nlp_data. nlconstr[ref. index. value] = JuMP. _NonlinearConstraint (JuMP. _NonlinearExprData (m, pers_func_expr), lower_b, upper_b)
231
-
232
- # NOTE: the new NLconstraint cannot be assigned a name (not an option in add_NL_constraint)
233
- # pers_func_name = Symbol("perspective_func_$(disj_name)$(j)$(k)")
281
+ replace_operators! (expr)
282
+ if ref isa NonlinearConstraintRef
283
+ # determine bounds of original constraint (note: if op is ==, both bounds are set to rhs)
284
+ upper_b = (op == >= ) ? Inf : rhs
285
+ lower_b = (op == <= ) ? - Inf : rhs
286
+ # replace NL constraint currently in the model with the reformulated one
287
+ m. nlp_data. nlconstr[ref. index. value] = JuMP. _NonlinearConstraint (JuMP. _NonlinearExprData (m, expr), lower_b, upper_b)
288
+ elseif ref isa ConstraintRef
289
+ # add a nonlinear constraint with the perspective function
290
+ add_NL_constraint (m, expr)
291
+ # delete old constraint
292
+ delete (m, ref)
293
+ end
234
294
end
235
295
236
296
function replace_JuMPvars! (expr, model)
0 commit comments