Skip to content

Commit 0f678ca

Browse files
committed
upgrade package
close Quadratic constraints that are modeled using `@constraint` #21 close Big-M for nonlinear constraints #20 close Need to delete NLconstraint after reformulating its respective perspective function when using CHR #7
1 parent d66073b commit 0f678ca

File tree

3 files changed

+124
-54
lines changed

3 files changed

+124
-54
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DisjunctiveProgramming"
22
uuid = "0d27d021-0159-4c7d-b4a7-9ccb5d9366cf"
33
authors = ["hdavid16 <[email protected]>"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"

examples/ex5.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using JuMP
2+
using DisjunctiveProgramming
3+
4+
m = Model()
5+
@variable(m, -10 x 10)
6+
7+
nl_con1 = @NLconstraint(m, exp(x) >= 1)
8+
nl_con2 = @NLconstraint(m, exp(x) <= 2)
9+
10+
@disjunction(m, nl_con1, nl_con2, reformulation = :CHR, name=:z)

src/reformulate.jl

Lines changed: 113 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,40 @@ function reformulate(m, disj, bin_var, reformulation, param)
44
for (i,constr) in enumerate(disj)
55
if constr isa Vector || constr isa Tuple
66
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)
88
end
99
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)
1111
end
1212
end
1313
if reformulation == :CHR
1414
add_disaggregated_constr(m, disj, vars)
1515
end
1616
end
1717

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)
1919
param = get_reform_param(param, i, j) #M or eps
2020
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)
2222
elseif typeof(constr) <: Array
2323
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)
2525
end
2626
elseif constr isa JuMP.Containers.DenseAxisArray
2727
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)
2929
end
3030
end
3131
end
3232

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+
3341
function get_reform_param(param, i, j)
3442
if param isa Number || ismissing(param)
3543
param = param
@@ -48,7 +56,7 @@ function get_reform_param(param, i, j)
4856
end
4957
end
5058

51-
function BMR(m, constr, bin_var, i, j, k, M)
59+
function BMR!(m, constr, bin_var, i, k, M)
5260
if ismissing(k)
5361
@assert is_valid(m,constr) "$constr is not a valid constraint in the model."
5462
ref = constr
@@ -58,14 +66,13 @@ function BMR(m, constr, bin_var, i, j, k, M)
5866
end
5967
if ismissing(M)
6068
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."
6270
end
71+
bin_var_ref = variable_by_name(ref.model, "$bin_var[$i]")
6372
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)
6976
end
7077
end
7178

@@ -108,7 +115,34 @@ function apply_interval_arithmetic(ref)
108115
end
109116
end
110117

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)
112146
if ismissing(k)
113147
if constr isa NonlinearConstraintRef #NOTE: CAN'T CHECK IF NL CONSTR IS VALID
114148
# @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)
124158
end
125159
ref = constr[k...]
126160
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]")
128162
for var in m[:original_model_variables]
129163
#get bounds for disaggregated variable
130164
@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)
141175
end
142176
end
143177
#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)
146180
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)
148182
end
149183
end
150184

151-
function lin_perspective_function(ref, bin_var_ref, i, j, k, eps)
185+
function lin_perspective_function(ref, bin_var_ref, i)
152186
#check constraint type
153187
ref_obj = constraint_object(ref)
154188
@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)
166200
end
167201
end
168202

169-
function nl_perspective_function(ref, bin_var_ref, i, j, k, eps)
203+
function nl_perspective_function(ref, bin_var_ref, i, eps)
170204
#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]
176206

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)
182208
sym_vars = []
209+
sym_i_vars = []
183210
for var in vars
184-
var_sym = Symbol(var)
211+
var_sym = Symbol(var) #original variable
185212
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])
186215
end
187216
ϵ = eps #epsilon parameter for perspective function (See Furman, Sawaya, Grossmann [2020] perspecive function)
188217
λ = Num(Symbolics.Sym{Float64}(Symbol(bin_var_ref)))
189218
FSG1 = Num(Symbolics.Sym{Float64}(gensym())) #this will become: [(1-ϵ)⋅λ + ϵ] (See Furman, Sawaya, Grossmann [2020] perspecive function)
190219
FSG2 = Num(Symbolics.Sym{Float64}(gensym())) #this will become: ϵ⋅(1-λ) (See Furman, Sawaya, Grossmann [2020] perspecive function)
191220

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
200224

201225
#use symbolic substitution to obtain the following expression:
202226
#[(1-ϵ)⋅λ + ϵ]⋅g(v/[(1-ϵ)⋅λ + ϵ]) - ϵ⋅g(0)⋅(1-λ) <= 0
203227
#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)))
205229
#second term
206230
g2 = FSG2*substitute(gx, Dict(var => 0 for var in sym_vars))
207231
#create perspective function and simplify
@@ -211,26 +235,62 @@ function nl_perspective_function(ref, bin_var_ref, i, j, k, eps)
211235
FSG2 => ϵ*(1-λ)))
212236
pers_func = simplify(pers_func)
213237

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
216267
#where any operators (i.e. exp, *, <=) are replaced by the actual
217268
#operator (not the symbol).
218269
#This is done to later replace the symbolic variables with JuMP variables,
219270
#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
221276

222277
#replace symbolic variables by their JuMP variables
223-
replace_JuMPvars!(pers_func_expr, m)
278+
m = ref.model
279+
replace_JuMPvars!(expr, m)
224280
#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
234294
end
235295

236296
function replace_JuMPvars!(expr, model)

0 commit comments

Comments
 (0)