Skip to content

Commit cdd0c38

Browse files
committed
add checks
1 parent 59de2af commit cdd0c38

File tree

2 files changed

+60
-37
lines changed

2 files changed

+60
-37
lines changed

examples/ex2.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ using JuMP
33
using DisjunctiveProgramming
44

55
m = Model()
6-
@variable(m, -1<=x[1:2]<=10)
6+
@variable(m, 0<=x[1:2]<=10)
77

88
@constraint(m, con1[i=1:2], x[i]<=[3,4][i])
99
@constraint(m, con2, 0<=x[1])

src/reformulate.jl

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,36 @@ function reformulate(m, disj, bin_var, reformulation, M)
1616
end
1717

1818
function init_reformulation(m, constr, bin_var, reformulation, M, i, j = missing)
19-
if M isa Number || ismissing(M)
20-
M = M
21-
elseif M isa Vector || M isa Tuple
22-
if M[i] isa Number
23-
M = M[i]
24-
elseif M[i] isa Vector || M[i] isa Tuple
25-
@assert j <= length(M[i]) "If constraint specific M values are provided, a value must be provided for each constraint in disjunct $i."
26-
M = M[i][j]
19+
if reformulation == :BMR
20+
if M isa Number || ismissing(M)
21+
M = M
22+
elseif M isa Vector || M isa Tuple
23+
if M[i] isa Number
24+
M = M[i]
25+
elseif M[i] isa Vector || M[i] isa Tuple
26+
@assert j <= length(M[i]) "If constraint specific M values are provided, a value must be provided for each constraint in disjunct $i."
27+
M = M[i][j]
28+
else
29+
error("Invalid M parameter provided for disjunct $i.")
30+
end
2731
else
2832
error("Invalid M parameter provided for disjunct $i.")
2933
end
30-
else
31-
error("Invalid M parameter provided for disjunct $i.")
3234
end
3335
if constr isa ConstraintRef
34-
eval(:($reformulation($m, $constr, $M, $bin_var, $i)))
36+
eval(:($reformulation($m, $constr, $bin_var, $i, missing; M = $M)))
3537
elseif typeof(constr) <: Array
3638
for k in Iterators.product([1:s for s in size(constr)]...)
37-
eval(:($reformulation($m, $constr, $M, $bin_var, $i, $k)))
39+
eval(:($reformulation($m, $constr, $bin_var, $i, $k; M = $M)))
3840
end
3941
elseif constr isa JuMP.Containers.DenseAxisArray
4042
for k in Iterators.product([s for s in constr.axes]...)
41-
eval(:($reformulation($m, $constr, $M, $bin_var, $i, $k)))
43+
eval(:($reformulation($m, $constr, $M, $bin_var, $i, $k; M = $M)))
4244
end
4345
end
4446
end
4547

46-
function BMR(m, constr, M, bin_var, i, k = missing)
48+
function BMR(m, constr, bin_var, i, k = missing; M)
4749
if ismissing(k)
4850
@assert is_valid(m,constr) "$constr is not a valid constraint in the model."
4951
ref = constr
@@ -66,7 +68,7 @@ function apply_interval_arithmetic(ref)
6668
@assert length(findall(r"[<>]", ref_str)) <= 1 "$ref must be one of the following: GreaterThan, LessThan, or EqualTo."
6769
ref_func = replace(split(ref_str, r"[=<>]")[1], " " => "")
6870
ref_type = occursin(">", ref_str) ? :lower : :upper
69-
ref_rhs = parse(Float64,split(ref_str, " ")[end])
71+
ref_rhs = 0 #Could be calculated with: parse(Float64,split(ref_str, " ")[end]). NOTE: @NLconstraint will always have a 0 RHS.
7072
elseif ref isa ConstraintRef
7173
ref_obj = constraint_object(ref)
7274
@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."
@@ -102,7 +104,7 @@ function apply_interval_arithmetic(ref)
102104
end
103105
end
104106

105-
function CHR(m, constr, M, bin_var, i, k = missing)
107+
function CHR(m, constr, bin_var, i, k = missing; M = missing)
106108
if ismissing(k)
107109
@assert is_valid(m,constr) "$constr is not a valid constraint in the model."
108110
ref = constr
@@ -111,35 +113,54 @@ function CHR(m, constr, M, bin_var, i, k = missing)
111113
ref = constr[k...]
112114
end
113115
bin_var_ref = variable_by_name(ref.model, string(bin_var[i]))
114-
constr_set = constraint_object(ref).set
115-
set_fields = fieldnames(typeof(constr_set))
116-
@assert length(set_fields) == 1 "A reformulation cannot be done on constraint $ref because it is not one of the following GreaterThan, LessThan, or EqualTo."
117-
@assert :value in set_fields || :lower in set_fields || :upper in set_fields "$ref must be one the following: GreaterThan, LessThan, or EqualTo."
118116
for var in m[:original_model_variables]
119-
coeff = normalized_coefficient(ref,var)
120-
iszero(coeff) && continue
121-
if ismissing(M)
122-
if has_upper_bound(var)
123-
M = upper_bound(var)
124-
else
125-
error("Variable $var does not have an upper bound, an M value must be specified")
126-
end
127-
end
117+
#get bounds for disaggregated variable
118+
@assert has_upper_bound(var) "Variable $var does not have an upper bound."
119+
@assert has_lower_bound(var) "Variable $var does not have a lower bound."
120+
UB = upper_bound(var)
121+
LB = lower_bound(var)
128122
#create disaggregated variable
129123
var_i = Symbol("$(var)_$i")
130124
if !(var_i in keys(m.obj_dict))
131-
eval(:(@variable($m, 0 <= $var_i <= $M)))
132-
eval(:(@constraint($m, $var_i <= $M * $bin_var_ref)))
125+
eval(:(@variable($m, $LB <= $var_i <= $UB)))
126+
eval(:(@constraint($m, $LB * $bin_var_ref <= $var_i)))
127+
eval(:(@constraint($m, $var_i <= $UB * $bin_var_ref)))
133128
end
134129
#create convex hull constraint
135-
rhs = normalized_rhs(ref)
136-
set_normalized_rhs(ref,0)
137-
set_normalized_coefficient(ref, var, 0)
138-
set_normalized_coefficient(ref, m[var_i], coeff)
139-
set_normalized_coefficient(ref, bin_var_ref, -rhs)
130+
if ref isa NonlinearConstraintRef
131+
nl_perspective_function(ref, bin_var_ref)
132+
elseif ref isa ConstraintRef
133+
lin_perspective_function(ref, bin_var_ref, var, i)
134+
end
140135
end
141136
end
142137

138+
function nl_perspective_function(ref, bin_var_ref)
139+
# [(1-ϵ)⋅λ + ϵ]⋅g(v/[(1-ϵ)⋅λ + ϵ]) - ϵ⋅g(0)⋅(1-λ) <= 0
140+
ref_str = string(ref)
141+
@assert length(findall(r"[<>]", ref_str)) <= 1 "$ref must be one of the following: GreaterThan, LessThan, or EqualTo."
142+
ref_func = replace(split(ref_str, r"[=<>]")[1], " " => "")
143+
ref_op = occursin(">=", ref_str) ? ">=" : (occursin("<=", ref_str) ? "<=" : "==")
144+
end
145+
146+
function lin_perspective_function(ref, bin_var_ref, var, i)
147+
#check constraint type
148+
ref_obj = constraint_object(ref)
149+
@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."
150+
#check var is present in the constraint
151+
coeff = normalized_coefficient(ref,var)
152+
iszero(coeff) && return
153+
#check CHR can be applied to this constraint
154+
rhs = normalized_rhs(ref) #get rhs
155+
@assert !iszero(rhs) "The convex hull reformulation cannot be done on constraint $ref because its right-hand-side is zero. Use Big-M instead."
156+
#modify constraint using convex hull
157+
var_i_ref = variable_by_name(ref.model, "$(var)_$i")
158+
set_normalized_rhs(ref,0) #set rhs to 0
159+
set_normalized_coefficient(ref, var, 0) #remove original variable
160+
set_normalized_coefficient(ref, var_i_ref, coeff) #add disaggregated variable
161+
set_normalized_coefficient(ref, bin_var_ref, -rhs) #add binary variable
162+
end
163+
143164
function add_disaggregated_constr(m, disj, vars)
144165
for var in vars
145166
d_vars = []
@@ -151,7 +172,9 @@ function add_disaggregated_constr(m, disj, vars)
151172
end
152173
end
153174

175+
################################################################################
154176
### DEPRECATED ###
177+
################################################################################
155178
function infer_BigM(ref)
156179
constr_set = constraint_object(ref).set
157180
set_fields = fieldnames(typeof(constr_set))

0 commit comments

Comments
 (0)