Skip to content

Commit fcf3844

Browse files
authored
Merge branch 'hdavid16:master' into master
2 parents 31e9dbc + 28bc985 commit fcf3844

File tree

15 files changed

+702
-598
lines changed

15 files changed

+702
-598
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.7"
4+
version = "0.3.0"
55

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

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ After defining a JuMP model, disjunctions can be added to the model by using the
1818
5. `Tuple` of expressions accepted by options 1 and/or 3.
1919

2020
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.
21+
- Vectorized constraints (using `.` notation) are not currently supported. The current workarround is to first create the constraint with the `@constraint` macro and then use the `add_disjunction!`, instead of the `@disjunction` macro. The `add_disjunction!` function receives the same arguments as the `@disjunction` macro, with the exception that instead of creating the constraints in the disjunctions, references to previously created constraints are used for the disjuncts.
2222
- Any constraints that are of `EqualTo` type are split into two constraints (e.g., `f(x) == 0` -> `0 <= f(x) <= 0`). This is necessary only for the Big-M reformulation of equality constraints, but is currently applied regardless of the reformulation technique.
2323
- Any constraints that are of `Interval` type are split into two constraints (one for each bound).
2424
- It is assumed that the disjuncts belonging to a disjunction are proper disjunctions (mutually exclussive) and only one of them will be selected (`XOR`).
2525

2626
The valid key-word arguments for the `@disjunction` macro are:
27-
- `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)
27+
- `reformulation::Symbol`: `:big_m` for [Big-M Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Big-M_Reformulation), `:hull` for [Hull Reformulation](https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities#Convex-Hull_Reformulation)
2828
- `M`: Big-M value used when `reformulation = :big_m`.
29-
- `ϵ`: 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`.
29+
- `ϵ`: 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 = :hull`.
3030
- `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)")]`.
3131

32-
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.
32+
When a disjunction is defined using the `@disjunction` macro, the disjunctions are reformulated to algebraic constraints via either Big-M or Hull reformulations. For the 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 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.
3333

3434
For empty disjuncts, use `nothing` for their positional argument (e.g., `@disjunction(m, x <= 1, nothing, reformulation = :big_m)`).
3535

@@ -51,7 +51,7 @@ The example below is from the [Northwestern University Process Optimization Open
5151

5252
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)`).
5353

54-
To perform the Convex-Hull reformulation, `reformulation = :convex_hull`. Variables must have bounds for the reformulation to work.
54+
To perform the Hull reformulation, `reformulation = :hull`. Variables must have bounds for the reformulation to work.
5555

5656
```julia
5757
using JuMP

examples/ex3.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ m = Model()
1313
3 exp(x)
1414
5 x
1515
end,
16-
reformulation=:convex_hull,
16+
reformulation=:hull,
1717
name=:z
1818
)
1919
print(m)
@@ -22,7 +22,6 @@ print(m)
2222
# Subject to
2323
# XOR(disj_z) : z[1] + z[2] == 1.0 <- XOR constraint
2424
# x_aggregation : x - x_z1 - x_z2 == 0.0 <- aggregation of disaggregated variables
25-
# disj_z[1,2] : -x_z1 - 3 z[1] <= 0.0 <- convex-hull reformulation of 2nd constraint if 1st disjunct (named disj_z[1,2] to indicate 1st disjunct, 2nd constraint)
2625
# x_z1_lb : -5 z[1] - x_z1 <= 0.0 <- lower-bound constraint on disaggregated variable x_z1 (x in 1st disjunct)
2726
# x_z1_ub : -10 z[1] + x_z1 <= 0.0 <- upper-bound constraint on disaggregated variable x_z1 (x in 1st disjunct)
2827
# x_z2_lb : -5 z[2] - x_z2 <= 0.0 <- lower-bound constraint on disaggregated variable x_z2 (x in 2nd disjunct)

src/DisjunctiveProgramming.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ using JuMP, IntervalArithmetic, Symbolics, Suppressor
44

55
export add_disjunction!, add_proposition!, reformulate_disjunction
66
export @disjunction, @proposition
7+
export choose!
78

89
include("constraint.jl")
910
include("logic.jl")
11+
include("bounds.jl")
1012
include("utils.jl")
11-
include("big_M.jl")
12-
include("convex_hull.jl")
13+
include("bigm.jl")
14+
include("hull.jl")
1315
include("reformulate.jl")
1416
include("macros.jl")
1517

src/big_M.jl

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/bigm.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
big_m_reformulation!(constr::ConstraintRef, bin_var, M, i, j, k)
3+
4+
Perform Big-M reformulation on a linear or quadratic constraint at index k of constraint j in disjunct i.
5+
6+
big_m_reformulation!(constr::NonlinearConstraintRef, bin_var, M, i, j, k)
7+
8+
Perform Big-M reformulaiton on a nonlinear constraint at index k of constraint j in disjunct i.
9+
10+
big_m_reformulation!(constr::AbstractArray{<:ConstraintRef}, bin_var, M, i, j, k)
11+
12+
Perform Big-M reformulation on a constraint at index k of constraint j in disjunct i.
13+
"""
14+
function big_m_reformulation!(constr::ConstraintRef, bin_var, M, i, j, k)
15+
M = get_reform_param(M, i, j, k; constr)
16+
bin_var_ref = constr.model[bin_var][i]
17+
add_to_function_constant(constr, -M)
18+
set_normalized_coefficient(constr, bin_var_ref , M)
19+
end
20+
function big_m_reformulation!(constr::NonlinearConstraintRef, bin_var, M, i, j, k)
21+
M = get_reform_param(M, i, j, k; constr)
22+
#create symbolic variables (using Symbolics.jl)
23+
for var_ref in constr.model[:gdp_variable_refs]
24+
var_sym = Symbol(var_ref)
25+
eval(:(Symbolics.@variables($var_sym)[1]))
26+
end
27+
bin_var_sym = Symbol("$bin_var[$i]")
28+
λ = Num(Symbolics.Sym{Float64}(bin_var_sym))
29+
30+
#parse constr
31+
op, lhs, rhs = parse_constraint(constr)
32+
replace_Symvars!(lhs, constr.model) #convert JuMP variables into Symbolic variables
33+
gx = eval(lhs) #convert the LHS of the constraint into a Symbolic expression
34+
gx = gx - M*(1-λ) #add bigM
35+
36+
#update constraint
37+
replace_constraint(constr, gx, op, rhs)
38+
end
39+
big_m_reformulation!(constr::AbstractArray{<:ConstraintRef}, bin_var, M, i, j, k) =
40+
big_m_reformulation(constr[k], bin_var, M, i, j, k)

src/bounds.jl

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
apply_interval_arithmetic(constr)
3+
4+
Apply interval arithmetic on a constraint to find the bounds on the constraint.
5+
"""
6+
function apply_interval_arithmetic(constr)
7+
#convert constraints into Expr to replace variables with interval sets and determine bounds
8+
constr_type, constr_func_expr, constr_rhs = parse_constraint(constr)
9+
#create a map of variables to their bounds
10+
interval_map = Dict()
11+
vars = all_variables(constr.model)#constr.model[:gdp_variable_constrs]
12+
obj_dict = object_dictionary(constr.model)
13+
bounds_dict = :variable_bounds_dict in keys(obj_dict) ? obj_dict[:variable_bounds_dict] : Dict() #NOTE: should pass as an keyword argument
14+
for var in vars
15+
LB, UB = get_bounds(var, bounds_dict)
16+
interval_map[string(var)] = LB..UB
17+
end
18+
constr_func_expr = replace_intevals!(constr_func_expr, interval_map)
19+
#get bounds on the entire expression
20+
func_bounds = eval(constr_func_expr)
21+
Mlo = func_bounds.lo - constr_rhs
22+
Mhi = func_bounds.hi - constr_rhs
23+
M = constr_type == :(<=) ? Mhi : Mlo
24+
isinf(M) && error("M parameter for $constr cannot be infered due to lack of variable bounds.")
25+
return M
26+
end
27+
28+
"""
29+
get_bounds(var::VariableRef)
30+
31+
Get bounds on a variable.
32+
33+
get_bounds(var, bounds_dict::Dict)
34+
35+
Get bounds on a variable. Check if a bounds dictionary has been provided with bounds for that value.
36+
37+
get_bounds(var::AbstractArray{VariableRef}, bounds_dict::Dict, LB, UB)
38+
39+
Update lower bound `LB` and upper bound `UB` on a variable container.
40+
"""
41+
function get_bounds(var::VariableRef)
42+
LB = has_lower_bound(var) ? lower_bound(var) : (is_binary(var) ? 0 : -Inf)
43+
UB = has_upper_bound(var) ? upper_bound(var) : (is_binary(var) ? 1 : Inf)
44+
return LB, UB
45+
end
46+
function get_bounds(var::VariableRef, bounds_dict::Dict)
47+
if string(var) in keys(bounds_dict)
48+
return bounds_dict[string(var)]
49+
else
50+
return get_bounds(var)
51+
end
52+
end
53+
function get_bounds(var::AbstractArray{VariableRef}, bounds_dict::Dict, LB, UB)
54+
#populate UB and LB
55+
for idx in eachindex(var)
56+
LB[idx], UB[idx] = get_bounds(var[idx], bounds_dict)
57+
end
58+
return LB, UB
59+
end
60+
function get_bounds(var::Array{VariableRef}, bounds_dict::Dict)
61+
#initialize
62+
LB, UB = zeros(size(var)), zeros(size(var))
63+
return get_bounds(var, bounds_dict, LB, UB)
64+
end
65+
function get_bounds(var::Containers.DenseAxisArray, bounds_dict::Dict)
66+
#initialize
67+
LB = Containers.DenseAxisArray(zeros(size(var)), axes(var)...)
68+
UB = Containers.DenseAxisArray(zeros(size(var)), axes(var)...)
69+
return get_bounds(var, bounds_dict, LB, UB)
70+
end
71+
function get_gounds(var::Containers.SparseAxisArray, bounds_dict::Dict)
72+
#initialize
73+
idxs = keys(var.data)
74+
LB = Containers.SparseAxisArray(Dict(idx => 0. for idx in idxs))
75+
UB = Containers.SparseAxisArray(Dict(idx => 0. for idx in idxs))
76+
return get_bounds(var, bounds_dict, LB, UB)
77+
end

0 commit comments

Comments
 (0)