Skip to content

Commit 6ab70a6

Browse files
authored
Basic steps implementation (#141)
* Initial * Support proper basic steps on infinite disjunctions * Clean up functions * Infiniteopt ext * Warning messages and code coverage * Recoverable info * _set_support refactor + warning on shared indicators * Rebase with main.
1 parent 10c37f7 commit 6ab70a6

12 files changed

Lines changed: 983 additions & 16 deletions

‎README.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,39 @@ The following reformulation methods are currently supported:
194194
- `final_reform_method`: Reformulation method to apply after cutting plane iterations. Default: `BigM()`.
195195
- `M_value`: Big-M value to use in the relaxed Big-M reformulation during iterations. Default: `1e9`.
196196

197+
## Basic Steps
198+
199+
A [basic step](https://epubs.siam.org/doi/10.1137/0606047) intersects two or more disjunctions into a single one whose disjuncts are the intersections of the original disjuncts, taken over their cartesian product. The feasible region is unchanged, but the hull relaxation of the result is at least as tight as before. Global constraints can also be intersected into a disjunction (an improper basic step), which is done with the `constraints` keyword argument:
200+
201+
```julia
202+
using DisjunctiveProgramming, HiGHS
203+
204+
model = GDPModel(HiGHS.Optimizer)
205+
@variable(model, 0 <= x <= 5)
206+
@variable(model, Y[1:2], Logical)
207+
@variable(model, Z[1:2], Logical)
208+
@objective(model, Max, x)
209+
@constraint(model, x <= 1, Disjunct(Y[1]))
210+
@constraint(model, 2 <= x <= 3, Disjunct(Y[2]))
211+
@constraint(model, x <= 1, Disjunct(Z[1]))
212+
@constraint(model, 4 <= x <= 5, Disjunct(Z[2]))
213+
d1 = disjunction(model, Y)
214+
d2 = disjunction(model, Z)
215+
216+
# replace both disjunctions with their 4 term product disjunction
217+
apply_basic_step(model, [d1, d2])
218+
219+
optimize!(model, gdp_method = Hull())
220+
```
221+
222+
`apply_basic_step` deletes the original disjunctions and their disjunct constraints, but keeps the original logical variables and ties them to the new product indicators with `Exactly` constraints, so any reformulation method can be used afterwards. The indicators of the disjuncts that were intersected to form a product disjunct can be queried with `product_parents(w)`, where `w` is one of the indicators of the returned disjunction. It accepts the following optional arguments:
223+
224+
- `constraints`: Global constraints to intersect into every disjunct of the new disjunction. They are deleted from the model afterwards.
225+
- `name`: Base name for the product disjunction, its indicator variables, and the linking constraints. Anonymous by default.
226+
- `relax_products`: If `true`, the binary variables of the product indicators are relaxed to `[0, 1]`. This is valid because the original indicators remain binary and force the products integral, and it avoids growing the number of binary variables.
227+
228+
Note that the number of disjuncts of the product disjunction is the product of those of the inputs, so repeated basic steps grow the model multiplicatively. Intersecting disjunctions that share variables (or that are coupled by an intersected global constraint) is what tightens the relaxation; disjunctions over disjoint variables gain nothing and only pay the size cost.
229+
197230
## Conic Constraints
198231

199232
Disjunct constraints can also be defined with the conic sets `SecondOrderCone`, `RotatedSecondOrderCone`, `MOI.ExponentialCone`, and `MOI.PowerCone`. Big-M reformulates these by relaxing the constraint function along a fixed interior direction of the cone, and Hull produces the exact conic hull of [Bernal Neira & Grossmann [2021]](https://arxiv.org/abs/2109.09657).

‎ext/InfiniteDisjunctiveProgramming.jl‎

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ end
2626
################################################################################
2727
DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...))
2828

29+
const _InfExpr = Union{
30+
InfiniteOpt.GeneralVariableRef,
31+
JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef},
32+
JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef},
33+
JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef}
34+
}
35+
36+
# The parameters of every expression, in the order encountered
37+
function _union_parameter_refs(exprs)
38+
return unique!([pref for expr in exprs
39+
for pref in InfiniteOpt.parameter_refs(expr)])
40+
end
41+
42+
# A product indicator varies over the parameters of its parents, so the
43+
# linking constraints stay pointwise-consistent with the originals
44+
function DP.product_indicator_variable(
45+
model::InfiniteOpt.InfiniteModel,
46+
parents
47+
)
48+
prefs = _union_parameter_refs(DP.binary_variable(p) for p in parents)
49+
lvar = DP.LogicalVariable(nothing, nothing, nothing)
50+
isempty(prefs) && return lvar
51+
return DP._TaggedLogicalVariable(lvar, InfiniteOpt.Infinite(prefs...))
52+
end
53+
2954
_is_parameter(vref::InfiniteOpt.GeneralVariableRef) =
3055
_is_parameter(InfiniteOpt.dispatch_variable_ref(vref))
3156
_is_parameter(::InfiniteOpt.DependentParameterRef) = true
@@ -88,21 +113,8 @@ function DP.VariableProperties(
88113
return DP.VariableProperties(info, "", nothing, var_type)
89114
end
90115

91-
function DP.VariableProperties(
92-
exprs::Vector{<:Union{
93-
InfiniteOpt.GeneralVariableRef,
94-
JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef},
95-
JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef},
96-
JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef}
97-
}}
98-
)
99-
all_prefs = Set{InfiniteOpt.GeneralVariableRef}()
100-
for expr in exprs
101-
for pref in InfiniteOpt.parameter_refs(expr)
102-
push!(all_prefs, pref)
103-
end
104-
end
105-
prefs = Tuple(all_prefs)
116+
function DP.VariableProperties(exprs::AbstractVector{<:_InfExpr})
117+
prefs = _union_parameter_refs(exprs)
106118
info = DP._free_variable_info()
107119
var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing
108120
return DP.VariableProperties(info, "", nothing, var_type)
@@ -159,6 +171,34 @@ function JuMP.add_constraint(
159171
error("Cannot add, subtract, or multiply with logical variables.")
160172
end
161173

174+
# Resolve the ambiguity between InfiniteOpt's generic
175+
# `delete(::InfiniteModel, vref)` and DP's typed deletes (needed when a
176+
# basic step deletes its inputs). The ref types must be concrete, since
177+
# a `Union` of them is not specific enough.
178+
function JuMP.delete(
179+
m::InfiniteOpt.InfiniteModel, cref::DP.DisjunctionRef
180+
)
181+
return DP._delete(m, cref)
182+
end
183+
184+
function JuMP.delete(
185+
m::InfiniteOpt.InfiniteModel, cref::DP.DisjunctConstraintRef
186+
)
187+
return DP._delete(m, cref)
188+
end
189+
190+
function JuMP.delete(
191+
m::InfiniteOpt.InfiniteModel, cref::DP.LogicalConstraintRef
192+
)
193+
return DP._delete(m, cref)
194+
end
195+
196+
function JuMP.delete(
197+
m::InfiniteOpt.InfiniteModel, vref::DP.LogicalVariableRef
198+
)
199+
return DP._delete(m, vref)
200+
end
201+
162202
################################################################################
163203
# METHODS
164204
################################################################################

‎src/DisjunctiveProgramming.jl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ include("print.jl")
3232
include("extension_api.jl")
3333
include("utilities.jl")
3434
include("psplit.jl")
35+
include("basicsteps.jl")
3536

3637
# Define additional stuff that should not be exported
3738
const _EXCLUDE_SYMBOLS = [Symbol(@__MODULE__), :eval, :include]

0 commit comments

Comments
 (0)