Skip to content

Commit cee5c22

Browse files
author
shiv-666
committed
Reformulate nonlinear constraints correctly for CHR
Manually replaces nonlinear constraints in model with changed constraints when applying convex hull reformulation. Ensures that a model is given at least two constraints.
1 parent f1f1a39 commit cee5c22

File tree

2 files changed

+40
-16
lines changed

2 files changed

+40
-16
lines changed

src/macro.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ macro disjunction(args...)
55
reformulation = filter(i -> i.args[1] == :reformulation, kw_args)
66
if !isempty(reformulation)
77
reformulation = reformulation[1].args[2]
8+
if length(pos_args[2:end]) < 2
9+
throw(DomainError(args, "At least 2 constraints expected"))
10+
end
811
else
912
throw(UndefKeywordError(:reformulation))
1013
end
@@ -18,7 +21,7 @@ macro disjunction(args...)
1821
#get args
1922
m = esc(pos_args[1])
2023
disj = [esc(a) for a in pos_args[2:end]]
21-
24+
2225
#build disjunction
2326
:(add_disjunction($m, $(disj...), reformulation = $reformulation, M = $M, eps = $eps, name = $name))
2427
end

src/reformulate.jl

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ function BMR(m, constr, bin_var, i, j, k, M)
6666
end
6767

6868
function apply_interval_arithmetic(ref)
69+
#convert constraints into Expr to replace variables with interval sets and determine bounds
6970
if ref isa NonlinearConstraintRef
7071
ref_str = string(ref)
7172
@assert length(findall(r"[<>]", ref_str)) <= 1 "$ref must be one of the following: GreaterThan, LessThan, or EqualTo."
@@ -79,16 +80,20 @@ function apply_interval_arithmetic(ref)
7980
ref_type = fieldnames(typeof(ref_obj.set))[1]
8081
ref_rhs = normalized_rhs(ref)
8182
end
83+
ref_func_expr = Meta.parse(ref_func)
84+
#create a map of variables to their bounds
85+
interval_map = Dict()
8286
vars = all_variables(ref.model) #get all variable names
8387
for var in vars
8488
ub = has_upper_bound(var) ? upper_bound(var) : (is_binary(var) ? 1 : Inf)
8589
lb = has_lower_bound(var) ? lower_bound(var) : (is_binary(var) ? 0 : Inf)
86-
ref_func = replace(ref_func, "$var" => "($lb..$ub)")
90+
interval_map[string(var)] = lb..ub
8791
end
88-
func_bounds = eval(Meta.parse(ref_func))
92+
ref_func_expr = replace_vars!(ref_func_expr, interval_map)
93+
#get bounds on the entire expression
94+
func_bounds = eval(ref_func_expr)
8995
if ref_type == :lower
9096
M = func_bounds.lo - ref_rhs
91-
9297
else
9398
M = func_bounds.hi - ref_rhs
9499
end
@@ -207,27 +212,28 @@ function nl_perspective_function(ref, bin_var_ref, i, j, k, eps)
207212
#operator (not the symbol).
208213
#This is done to later replace the symbolic variables with JuMP variables,
209214
#without messing with the math operators.
210-
pers_func_expr = Base.remove_linenums!(build_function(op(pers_func,rhs))).args[2].args[1]
215+
pers_func_expr = Base.remove_linenums!(build_function(pers_func)).args[2].args[1]
211216

212217
#replace symbolic variables by their JuMP variables
213218
replace_JuMPvars!(pers_func_expr, m)
214219
#replace the math operators by symbols
215220
replace_operators!(pers_func_expr)
216-
#add the constraint
217-
add_NL_constraint(m, pers_func_expr)
218-
219-
#NOTE: the NLconstraint defined by `ref` needs to be deleted. However, this
220-
# is not currently possible: https://github.com/jump-dev/JuMP.jl/issues/2355.
221-
# As of today (5/12/21), JuMP is behind on its support for nonlinear systems.
222-
221+
# determine bounds of original constraint
222+
upper_b = (op == >=) ? Inf : rhs
223+
lower_b = (op == <=) ? -Inf : rhs
224+
# replace NL constraint currently in the model with the reformulated one
225+
new = JuMP._NonlinearConstraint(JuMP._NonlinearExprData(m, pers_func_expr),
226+
lower_b, upper_b)
227+
m.nlp_data.nlconstr[ref.index.value] = new
228+
223229
#NOTE: the new NLconstraint cannot be assigned a name (not an option in add_NL_constraint)
224230
# pers_func_name = Symbol("perspective_func_$(disj_name)$(j)$(k)")
225231
end
226232

227233
function replace_JuMPvars!(expr, model)
228-
if expr isa Symbol
234+
if expr isa Symbol #replace symbolic variables with JuMP variables
229235
return variable_by_name(model, string(expr))
230-
elseif expr isa Expr
236+
elseif expr isa Expr #run recursion
231237
for i in eachindex(expr.args)
232238
expr.args[i] = replace_JuMPvars!(expr.args[i], model)
233239
end
@@ -236,16 +242,31 @@ function replace_JuMPvars!(expr, model)
236242
end
237243

238244
function replace_operators!(expr)
239-
if expr isa Expr
245+
if expr isa Expr #run recursion
240246
for i in eachindex(expr.args)
241247
expr.args[i] = replace_operators!(expr.args[i])
242248
end
243-
elseif !isa(expr, Symbol) && !isa(expr, Number) && !isa(expr, VariableRef)
249+
elseif expr isa Function #replace Function with its symbol
244250
return Symbol(expr)
245251
end
246252
expr
247253
end
248254

255+
function replace_vars!(expr, intervals)
256+
if string(expr) in keys(intervals) #check if expression is one of the model variables in the intervals dict
257+
return intervals[string(expr)] #replace expression with interval
258+
elseif expr isa Expr
259+
if length(expr.args) == 1 #run recursive relation on the leaf node on expression tree
260+
expr.args[i] = replace_vars!(expr.args[i], intervals)
261+
else #run recursive relation on each internal node of the expression tree, but skip the first element, which will always be the operator (this will avoid issues if the user creates a model variable called exp)
262+
for i in 2:length(expr.args)
263+
expr.args[i] = replace_vars!(expr.args[i], intervals)
264+
end
265+
end
266+
end
267+
expr
268+
end
269+
249270
function add_disaggregated_constr(m, disj, vars)
250271
for var in vars
251272
d_vars = []

0 commit comments

Comments
 (0)