Skip to content

Commit 6b37cf7

Browse files
committed
renaming of key word argument for reformulation (:big_m, :convex_hull)
1 parent 8435f57 commit 6b37cf7

File tree

9 files changed

+78
-28
lines changed

9 files changed

+78
-28
lines changed

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ After defining a JuMP model, disjunctions can be added to the model by using the
1717
4. A valid expression accepted by [JuMP.@NLconstraints](https://jump.dev/JuMP.jl/stable/reference/nlp/#JuMP.@NLconstraints) (using `begin...end)
1818
5. `Tuple` of expressions accepted by options 1 and/or 3.
1919

20-
NOTE: Any constraints that are of `Interval` type are split into two constraints (one for each bound). It is assumed that the disjuncts belonging to a disjunction are proper disjunctions (mutually exclussive) and only one of them will be selected (`XOR`).
20+
NOTES:
21+
- Vectorized constraints (using `.` notation) are not currently supported. The current workarround is to first creating the constraint outside of the `@disjunction` macro and then passing the reference to the constraint to the `@disjunction` macro.
22+
- Any constraints that are of `Interval` type are split into two constraints (one for each bound). It is assumed that the disjuncts belonging to a disjunction are proper disjunctions (mutually exclussive) and only one of them will be selected (`XOR`).
2123

2224
The valid key-word arguments for the `@disjunction` macro are:
23-
- `reformulation::Symbol`: `:BMR` for [Big-M Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Big-M_Reformulation), `:CHR` for [Convex-Hull Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Convex-Hull_Reformulation)
24-
- `M`: Big-M value used when `reformulation = :BMR`.
25-
- `ϵ`: epsilon tolerance for the perspective function proposed by [Furman, et al. [2020]](https://link.springer.com/article/10.1007/s10589-020-00176-0). Only used when `reformulation = :CHR`.
25+
- `reformulation::Symbol`: `:big_m` for [Big-M Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Big-M_Reformulation), `:convex_hull` for [Convex-Hull Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Convex-Hull_Reformulation)
26+
- `M`: Big-M value used when `reformulation = :big_m`.
27+
- `ϵ`: epsilon tolerance for the perspective function proposed by [Furman, et al. [2020]](https://link.springer.com/article/10.1007/s10589-020-00176-0). Only used when `reformulation = :convex_hull`.
2628
- `name::Symbol`: Name for the disjunction (also name for indicator variable used on that disjunction). If not passed (`name = missing`), a symbolic name will be generated with the prefix `disj`. The mutual exclussion constraint on the binary indicator variables can be accessed with `model[Symbol("XOR(disj_$name)")]`.
2729

2830
When a disjunction is defined using the `@disjunction` macro, the disjunctions are reformulated to algebraic constraints via either Big-M or Convex-Hull reformulations. For the Convex-Hull reformulation, disaggregated variables are generated by adding the suffix `_$name$i` to the original variables, where `i` is the index of the disjunct in that disjunction. Bounding constraints are applied to the disaggregated variables and can be accessed with `model[Symbol("$<original var>_$name$i_lb")]` and `model[Symbol("$<original var>_$name$i_ub")]` for the lower bound and upper bound constraints, respectively. The aggregation constraint can be accessed with `model[Symbol("$<original var>_aggregation")]`. For Big-M reformulations, the user may provide an `M` object that represents the BigM value(s). The `M` object can be a `Number` that is applied to all constraints in the disjuncts, or a `Vector`/`Tuple` of values that are used for each of the disjuncts. For Convex-Hull reformulations, the user may provide an `ϵ` value for the perspective function (default is `ϵ = 1e-6`). The `ϵ` object can be a `Number` that is applied to all perspective functions, or a `Vector`/`Tuple` of values that are used for each of the disjuncts.
2931

30-
For empty disjuncts, use `nothing` for their positional argument (e.g., `@disjunction(m, x <= 1, nothing, reformulation = :BMR)`).
32+
For empty disjuncts, use `nothing` for their positional argument (e.g., `@disjunction(m, x <= 1, nothing, reformulation = :big_m)`).
3133

3234
NOTE: `:gdp_variable_refs` and `:gdp_variable_names` are forbidden JuMP model object names when using *DisjunctiveProgramming.jl*. They are used to store the variable names and variable references in the original model.
3335

@@ -45,9 +47,9 @@ The logical proposition is then internally reformulated to an algebraic constrai
4547

4648
The example below is from the [Northwestern University Process Optimization Open Textbook](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities).
4749

48-
To perform the Big-M reformulation, `:BMR` is passed to the `reformulation` keyword argument. If nothing is passed to the keyword argument `M`, tight Big-M values will be inferred from the variable bounds using IntervalArithmetic.jl. If `x` is not bounded, Big-M values must be provided for either the whole system (e.g., `M = 10`) or for each of the constraint arrays in the example (e.g., `M = (10,10)`).
50+
To perform the Big-M reformulation, `:big_m` is passed to the `reformulation` keyword argument. If nothing is passed to the keyword argument `M`, tight Big-M values will be inferred from the variable bounds using IntervalArithmetic.jl. If `x` is not bounded, Big-M values must be provided for either the whole system (e.g., `M = 10`) or for each of the constraint arrays in the example (e.g., `M = (10,10)`).
4951

50-
To perform the Convex-Hull reformulation, `reformulation = :CHR`. Variables must have bounds for the reformulation to work.
52+
To perform the Convex-Hull reformulation, `reformulation = :convex_hull`. Variables must have bounds for the reformulation to work.
5153

5254
```julia
5355
using JuMP
@@ -59,7 +61,7 @@ m = Model()
5961
m,
6062
0 x 3,
6163
5 x 9,
62-
reformulation=:BMR,
64+
reformulation=:big_m,
6365
name=:y
6466
)
6567
@proposition(m, y[1] y[2]) #this is a redundant proposition

examples/ex1.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ m = Model()
77
m,
88
0 x 3,
99
5 x 9,
10-
reformulation=:BMR,
10+
reformulation=:big_m,
1111
name=:y
1212
)
1313
@proposition(m, y[1] y[2]) #this is a redundant proposition
@@ -26,5 +26,9 @@ print(m)
2626
# disj_y[2][ub] : x + y[2] <= 10.0 <- right-side of constraint in 2nd disjunct (name is assigned to disj_y[2][ub])
2727
# x >= -5.0 <- variable lower bound
2828
# x <= 10.0 <- variable upper bound
29+
# y[1] >= 0.0 <- lower bound on binary
30+
# y[2] >= 0.0 <- lower bound on binary
31+
# y[1] <= 1.0 <- upper bound on binary
32+
# y[2] <= 1.0 <- upper bound on binary
2933
# y[1] binary <- indicator variable (1st disjunct) is binary
3034
# y[2] binary <- indicator variable (2nd disjunct) is binary

examples/ex2.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ m = Model()
1212
begin
1313
con2[i=1:2], [5,4][i] x[i] [9,6][i]
1414
end,
15-
reformulation = :BMR,
15+
reformulation = :big_m,
1616
name = :y
1717
)
1818
print(m)
@@ -34,5 +34,9 @@ print(m)
3434
# x[2] >= -5.0 <- variable bounds
3535
# x[1] <= 10.0 <- variable bounds
3636
# x[2] <= 10.0 <- variable bounds
37+
# y[1] >= 0.0 <- lower bound on binary
38+
# y[2] >= 0.0 <- lower bound on binary
39+
# y[1] <= 1.0 <- upper bound on binary
40+
# y[2] <= 1.0 <- upper bound on binary
3741
# y[1] binary <- indicator variable (1st disjunct) is binary
3842
# y[2] binary <- indicator variable (2nd disjunct) is binary

examples/ex3.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ m = Model()
55
@variable(m, -5 x 10)
66
@disjunction(
77
m,
8+
# begin
9+
# exp(x) ≤ 2
10+
# -3 ≤ x
11+
# end,
812
(
913
exp(x) 2,
1014
-3 x
@@ -13,7 +17,7 @@ m = Model()
1317
3 exp(x)
1418
5 x
1519
end,
16-
reformulation=:CHR,
20+
reformulation=:convex_hull,
1721
name=:z
1822
)
1923
print(m)

src/big_M.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function BMR!(constr, bin_var, i, k, M)
1+
function big_m_reformulation!!(constr, bin_var, i, k, M)
22
if ismissing(k)
33
ref = constr
44
else

src/convex_hull.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function CHR!(constr, bin_var, i, k, eps)
1+
function convex_hull_reformulation!!(constr, bin_var, i, k, eps)
22
ref = ismissing(k) ? constr : constr[k...] #get constraint
33
#create convex hull constraint
44
if ref isa NonlinearConstraintRef || constraint_object(ref).func isa QuadExpr

src/macros.jl

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ macro disjunction(m, args...)
88
if !isempty(reformulation)
99
reformulation = reformulation[1].args[2]
1010
reformulation_kind = eval(reformulation)
11-
@assert reformulation_kind in [:BMR, :CHR] "Invalid reformulation method passed to keyword argument `:reformulation`. Valid options are :BMR (Big-M Reformulation) and :CHR (Convex-Hull Reormulation)."
12-
if reformulation_kind == :BMR
11+
@assert reformulation_kind in [:big_m, :convex_hull] "Invalid reformulation method passed to keyword argument `:reformulation`. Valid options are :big_m (Big-M Reformulation) and :convex_hull (Convex-Hull Reormulation)."
12+
if reformulation_kind == :big_m
1313
M = filter(i -> i.args[1] == :M, kw_args)
1414
param = !isempty(M) ? M[1].args[2] : :(missing)
15-
elseif reformulation_kind == :CHR
15+
elseif reformulation_kind == :convex_hull
1616
ϵ = filter(i -> i.args[1] == , kw_args)
1717
param = !isempty(ϵ) ? ϵ[1].args[2] : :(1e-6)
1818
else
@@ -66,25 +66,61 @@ function add_disjunction_constraint(m, d, dname)
6666
@constraints($m,$d)
6767
catch e
6868
if e isa ErrorException
69-
for di in $d
70-
add_nonlinear_constraint($m,di)
71-
end
69+
@NLconstraints($m,$d)
7270
else
7371
throw(e)
7472
end
7573
end
74+
# for di in $d
75+
# try
76+
# if Meta.isexpr(di, :call)
77+
# op, lhs, rhs = d.args
78+
# set = op == :(<=) ? MOI.LessThan(0) : MOI.GreaterThan(0)
79+
# func = lhs - rhs
80+
# elseif Meta.isexpr(di, :comparison)
81+
# lb, op1, func, op2, ub = d.args
82+
# @assert op1 == op2
83+
# set = op1 == :(<=) ? MOI.Interval(lb,ub) : MOI.Interval(ub,lb)
84+
# end
85+
# add_constraint($m, ScalarConstraint(func,set), $dname)
86+
# catch e
87+
# if e isa ErrorException
88+
# add_nonlinear_constraint($m,di)
89+
# else
90+
# throw(e)
91+
# end
92+
# end
93+
# end
7694
end
7795
elseif Meta.isexpr(d, (:call, :comparison))
7896
d = quote
7997
try
8098
@constraint($m,$dname,$d)
8199
catch e
82100
if e isa ErrorException
83-
add_nonlinear_constraint($m,$d)
101+
@NLconstraint($m,$d)
84102
else
85103
throw(e)
86104
end
87105
end
106+
# try
107+
# if Meta.isexpr($d, :call)
108+
# op, lhs, rhs = d.args
109+
# set = op == :(<=) ? MOI.LessThan(0) : MOI.GreaterThan(0)
110+
# func = lhs - rhs
111+
# elseif Meta.isexpr($d, :comparison)
112+
# lb, op1, func, op2, ub = d.args
113+
# @assert op1 == op2
114+
# set = op1 == :(<=) ? MOI.Interval(lb,ub) : MOI.Interval(ub,lb)
115+
# end
116+
# add_constraint($m, ScalarConstraint(func,set), $dname)
117+
# catch e
118+
# if e isa ErrorException
119+
# add_nonlinear_constraint($m,$d)
120+
# else
121+
# throw(e)
122+
# end
123+
# end
88124
end
89125
end
90126

@@ -93,7 +129,7 @@ end
93129

94130
function add_disjunction!(m::Model,disj...;reformulation::Symbol,M=missing=1e-6,name=missing)
95131
#run checks
96-
@assert reformulation in [:BMR, :CHR] "Invalid reformulation method passed to keyword argument `:reformulation`. Valid options are :BMR (Big-M Reformulation) and :CHR (Convex-Hull Reormulation)."
132+
@assert reformulation in [:big_m, :convex_hull] "Invalid reformulation method passed to keyword argument `:reformulation`. Valid options are :big_m (Big-M Reformulation) and :convex_hull (Convex-Hull Reormulation)."
97133
@assert length(disj) > 1 "At least 2 disjuncts must be included. If there is an empty disjunct, use `nothing`."
98134

99135
#create binary indicator variables for each disjunction
@@ -108,7 +144,7 @@ function add_disjunction!(m::Model,disj...;reformulation::Symbol,M=missing,ϵ=1e
108144
m[Symbol(xor_con)] = @constraint(m, sum(m[bin_var]) == 1, base_name = xor_con)
109145

110146
#apply reformulation
111-
param = reformulation == :BMR ? M : ϵ
147+
param = reformulation == :big_m ? M : ϵ
112148
reformulate_disjunction(m, disj; bin_var, reformulation, param)
113149
end
114150

src/reformulate.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function reformulate_disjunction(m::Model, disj...; bin_var, reformulation, para
1111
@expression(m, gdp_variable_names, var_names)
1212
end
1313
#run reformulation
14-
if reformulation == :CHR
14+
if reformulation == :convex_hull
1515
disaggregate_variables(m, disj, bin_var)
1616
sum_disaggregated_variables(m, disj, bin_var)
1717
end
@@ -83,10 +83,10 @@ function apply_reformulation(constr, bin_var, reformulation, param, i, j = missi
8383
end
8484

8585
function call_reformulation(reformulation, constr, bin_var, i, k, param)
86-
if reformulation == :BMR
87-
BMR!(constr, bin_var, i, k, param)
88-
elseif reformulation == :CHR
89-
CHR!(constr, bin_var, i, k, param)
86+
if reformulation == :big_m
87+
big_m_reformulation!!(constr, bin_var, i, k, param)
88+
elseif reformulation == :convex_hull
89+
convex_hull_reformulation!!(constr, bin_var, i, k, param)
9090
end
9191
end
9292

src/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function replace_JuMPvars!(expr, model)
128128
end
129129

130130
function replace_operators!(expr)
131-
#replace operators with their symbol. NOTE: Is this still needed for the CHR of nl constraints? (check this)
131+
#replace operators with their symbol. NOTE: Is this still needed for the convex_hull_reformulation! of nl constraints? (check this)
132132
if expr isa Expr #run recursion
133133
for i in eachindex(expr.args)
134134
expr.args[i] = replace_operators!(expr.args[i])

0 commit comments

Comments
 (0)