Skip to content

Commit 049f4e7

Browse files
committed
_optimize_hook dispatch on AbstractReformulationMethod
1 parent 4c31ca6 commit 049f4e7

13 files changed

Lines changed: 184 additions & 126 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,12 @@ The following reformulation methods are currently supported:
184184

185185
All variables must be included in exactly one partition. For manual partitioning, ensure each variable appears in exactly one group. For automatic partitioning, variables are divided as evenly as possible among the specified number of partitions.
186186

187-
6. [Cutting Planes](https://pubsonline.informs.org/doi/10.1287/ijoc.2015.0669): This method iteratively generates cutting planes using a separation problem and a relaxed Big-M formulation, then applies a final reformulation method. The `CuttingPlanes` struct is created with the following arguments:
187+
6. [Cutting Planes](https://pubsonline.informs.org/doi/10.1287/ijoc.2015.0669): This method iteratively generates cutting planes using a separation problem and a relaxed Big-M formulation. The `CuttingPlanes` struct is created with the following arguments:
188188

189189
- `optimizer`: Optimizer to use when solving the separation and relaxed Big-M subproblems. This is a required value.
190190
- `max_iter`: Maximum number of cutting plane iterations. Default: `3`.
191191
- `seperation_tolerance`: Convergence tolerance for the separation problem objective. Default: `1e-6`.
192-
- `final_reform_method`: Reformulation method to apply after cutting plane iterations. Default: `BigM()`.
193-
- `M_value`: Big-M value to use in the relaxed Big-M reformulation during iterations. Default: `1e9`.
192+
- `M_value`: Big-M value to use in the relaxed Big-M reformulation. Default: `1e9`.
194193

195194
## Infinite-Dimensional GDP
196195
To model disjunctions, logical variables, and logical constraints with infinite-dimensional optimization problems (e.g., dynamic and stochastic optimization), DisjunctiveProgramming is also compatible with [InfiniteOpt.jl](https://github.com/infiniteopt/InfiniteOpt.jl). For this, the syntax is largely the same, users simply need to import `InfiniteOpt` and use `InfiniteGDPModel`. They also can use `InfiniteLogical` to declare infinite logical variables as shown below:

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ The following reformulation methods are currently supported:
171171

172172
5. [Indicator](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints): This method reformulates each disjunct constraint into an indicator constraint with the Boolean reformulation counterpart of the Logical variable used to define the disjunct constraint. This is invoked with [`Indicator`](@ref).
173173

174-
6. Logic-based Outer Approximation (LOA): An iterative solution method for nonlinear GDPs, rather than a single-shot reformulation. It alternates between a primal NLP (the model reformulated by an inner method — `BigM`, `MBM`, or `Hull` — with the disjunct binaries fixed at the current selection) and a master MILP that accumulates outer-approximation and no-good cuts until the bound meets the incumbent. This is invoked with the [`LOA`](@ref) struct, e.g. `optimize!(m, gdp_method = LOA(Ipopt.Optimizer))`.
174+
6. Logic-based Outer Approximation (LOA): An iterative solution method for nonlinear GDPs, rather than a single-shot reformulation. It alternates between a primal NLP (the model reformulated by an inner method — `BigM`, `MBM`, or `Hull` — with the disjunct binaries fixed at the current selection) and a master MILP that accumulates outer-approximation and no-good cuts until the bound meets the incumbent. This is invoked with the [`LOA`](@ref) struct, e.g. `optimize!(m, gdp_method = LOA(Ipopt.Optimizer))`. Unlike the reformulation methods above, `LOA` runs the full algorithm on every `optimize!` call and cannot be passed to `reformulate_model`.
175175

176176
## Release Notes
177177

ext/InfiniteDisjunctiveProgramming.jl

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -412,15 +412,10 @@ end
412412
# variable under an infinite indicator keys its single disaggregated
413413
# copy by each per-support binary reference, mirroring the per-support
414414
# disjunct cuts that look it up.
415-
_transcribed_disaggregation_map(::InfiniteOpt.InfiniteModel, ::Nothing) =
416-
nothing
417-
function _transcribed_disaggregation_map(
418-
model::InfiniteOpt.InfiniteModel,
419-
disaggregation_map
420-
)
415+
function _transcribed_disaggregation_map(model::InfiniteOpt.InfiniteModel)
421416
result = Dict{Tuple{JuMP.VariableRef,
422417
Union{JuMP.VariableRef, JuMP.AffExpr}}, JuMP.VariableRef}()
423-
for ((variable, indicator), disaggregated) in disaggregation_map
418+
for ((variable, indicator), disaggregated) in DP._disaggregation_map(model)
424419
binary_refs = _transcribed_binary_refs(model, indicator)
425420
variables = _transcribed_refs(
426421
InfiniteOpt.transformation_variable(variable))
@@ -437,8 +432,7 @@ end
437432

438433
function DP.build_loa_problem(
439434
model::InfiniteOpt.InfiniteModel,
440-
method::DP.LOA,
441-
disaggregation_map = nothing
435+
method::DP.LOA
442436
)
443437
InfiniteOpt.build_transformation_backend!(model)
444438
nlp = InfiniteOpt.transformation_model(model)
@@ -493,7 +487,7 @@ function DP.build_loa_problem(
493487

494488
return DP._LOAProblem(nlp, binaries,
495489
disjunct_constraints, global_constraints,
496-
_transcribed_disaggregation_map(model, disaggregation_map))
490+
_transcribed_disaggregation_map(model))
497491
end
498492

499493
end

src/datatypes.jl

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,8 @@ constraints.
417417
"""
418418
struct Hull{T} <: AbstractReformulationMethod
419419
value::T
420-
# Internal: LOA installs a Dict here to collect the disaggregation
421-
# map during reformulation; `nothing` for every other use.
422-
disaggregation_map::Union{Nothing, AbstractDict}
423-
function Hull::T = 1e-6; disaggregation_map = nothing) where {T}
424-
new{T}(ϵ, disaggregation_map)
420+
function Hull::T = 1e-6) where {T}
421+
new{T}(ϵ)
425422
end
426423
end
427424

@@ -430,13 +427,11 @@ mutable struct _Hull{V <: JuMP.AbstractVariableRef, T} <: AbstractReformulationM
430427
value::T
431428
disjunction_variables::Dict{V, Vector{V}}
432429
disjunct_variables::Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}
433-
disaggregation_map::Union{Nothing, AbstractDict}
434430
function _Hull(method::Hull{T}, vrefs::Set{V}) where {T, V <: JuMP.AbstractVariableRef}
435431
new{V, T}(
436432
method.value,
437433
Dict{V, Vector{V}}(vref => V[] for vref in vrefs),
438-
Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}(),
439-
method.disaggregation_map
434+
Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}()
440435
)
441436
end
442437
end
@@ -450,24 +445,20 @@ A type for using the cutting planes approach for disjunctive constraints.
450445
- `optimizer::O`: Optimizer to use when solving mini-models (required).
451446
- `max_iter::Int`: Number of iterations (default = `3`).
452447
- `seperation_tolerance::T`: Tolerance for the separation problem (default = `1e-6`).
453-
- `final_reform_method::AbstractReformulationMethod`: Final reformulation
454-
method to use after cutting planes (default = `BigM()`).
455448
- `M_value::T`: Big-M value to use in the final reformulation (default = `1e9`).
456449
"""
457450
struct CuttingPlanes{O, T} <: AbstractReformulationMethod
458451
optimizer::O;
459452
max_iter::Int
460453
seperation_tolerance::T
461-
final_reform_method::AbstractReformulationMethod
462454
M_value::T
463455
function CuttingPlanes(
464456
optimizer::O;
465457
max_iter::Int = 3,
466458
seperation_tolerance::T = 1e-6,
467-
final_reform_method = BigM(),
468459
M_value::T = 1e9
469460
) where {O, T}
470-
new{O, T}(optimizer, max_iter, seperation_tolerance, final_reform_method, M_value)
461+
new{O, T}(optimizer, max_iter, seperation_tolerance, M_value)
471462
end
472463
end
473464

@@ -575,13 +566,18 @@ struct Indicator <: AbstractReformulationMethod end
575566
# LOA
576567
################################################################################
577568
"""
578-
LOA{O, P, R, T} <: AbstractReformulationMethod
569+
LOA{O, P, R, T} <: AbstractSolutionMethod
579570
580571
Logic-based Outer Approximation solver for GDP models. Iterates a primary
581572
NLP (original model reformulated by `inner_method`, binaries fixed per
582573
iteration) and a master MILP accumulating OA and no-good cuts.
583574
`inner_method` is `BigM` (default), `MBM`, or `Hull`.
584575
576+
LOA is a solution algorithm, not a reformulation: it is invoked with
577+
`optimize!(model, gdp_method = LOA(...))`, always re-runs on `optimize!`,
578+
and is not accepted by [`reformulate_model`](@ref). After the run the
579+
incumbent is loaded into `model`, so JuMP solution queries work directly.
580+
585581
## Fields
586582
- `nlp_optimizer::O`: solver for the primary NLP.
587583
- `mip_optimizer::P`: solver for the master MILP (default `nlp_optimizer`).
@@ -599,10 +595,10 @@ iteration) and a master MILP accumulating OA and no-good cuts.
599595
- `slack_tol::Float64`: max total slack for which the bound still counts
600596
as converged (positive slack = nonconvex crossing, keep iterating).
601597
- `iteration_time_limit::Float64`: budget (s) for the iteration loop.
602-
- `time_limit::Float64`: overall budget (s) incl. the final solve
603-
(default 3600; `Inf` disables).
598+
- `time_limit::Float64`: overall budget (s) for the algorithm; the
599+
final loading solve is not capped (default 3600; `Inf` disables).
604600
"""
605-
struct LOA{O, P, R, T} <: AbstractReformulationMethod
601+
struct LOA{O, P, R, T} <: AbstractSolutionMethod
606602
nlp_optimizer::O
607603
mip_optimizer::P
608604
inner_method::R
@@ -647,7 +643,7 @@ end
647643
# is the binary or its `1 - y` complement expression), the nonlinear
648644
# global `(function, set)` pairs, and the `(variable, binary_ref) ->
649645
# disaggregated variable` map from an inner Hull reformulation
650-
# (`nothing` for Big-M / MBM). Built by `build_loa_problem`. The
646+
# (empty for Big-M / MBM). Built by `build_loa_problem`. The
651647
# set-covering seed generates its combinations on the fly from the
652648
# master, so none are stored here.
653649
struct _LOAProblem{M <: JuMP.AbstractModel, V <: JuMP.AbstractVariableRef, T}
@@ -657,8 +653,7 @@ struct _LOAProblem{M <: JuMP.AbstractModel, V <: JuMP.AbstractVariableRef, T}
657653
JuMP.AbstractJuMPScalar, _MOI.AbstractScalarSet}}
658654
global_constraints::Vector{Tuple{JuMP.AbstractJuMPScalar,
659655
_MOI.AbstractScalarSet}}
660-
disaggregation_map::Union{Nothing,
661-
Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}}
656+
disaggregation_map::Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}
662657

663658
# Inner constructor deriving the value type `T` from the NLP model.
664659
# `T` only appears inside `Union` field types, so the default
@@ -715,6 +710,10 @@ mutable struct GDPData{M <: JuMP.AbstractModel, V <: JuMP.AbstractVariableRef, C
715710
# Helpful metadata for most reformulations (not just one of them)
716711
variable_bounds::Dict{V, Tuple{T, T}}
717712

713+
# Hull disaggregations recorded during reformulation, consumed by
714+
# solution algorithms (LOA): (variable, indicator) => disaggregated
715+
disaggregation_map::Dict{Tuple{V, LogicalVariableRef{M}}, V}
716+
718717
# Reformulation variables and constraints
719718
reformulation_variables::Vector{V}
720719
reformulation_constraints::Vector{C}
@@ -735,6 +734,7 @@ mutable struct GDPData{M <: JuMP.AbstractModel, V <: JuMP.AbstractVariableRef, C
735734
Dict{LogicalVariableRef{M}, Vector{Union{DisjunctConstraintRef{M}, DisjunctionRef{M}}}}(),
736735
Dict{Union{DisjunctConstraintRef{M}, DisjunctionRef{M}}, LogicalVariableRef{M}}(),
737736
Dict{V, Tuple{T, T}}(),
737+
Dict{Tuple{V, LogicalVariableRef{M}}, V}(),
738738
Vector{V}(),
739739
Vector{C}(),
740740
nothing,

src/hull.jl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ function _disaggregate_variable(
4242
#temp storage
4343
push!(method.disjunction_variables[vref], dvref)
4444
method.disjunct_variables[vref, bvref] = dvref
45-
#record into the LOA disaggregation map when installed (LOA-Hull only)
46-
method.disaggregation_map === nothing ||
47-
(method.disaggregation_map[(vref, lvref)] = dvref)
45+
#record the disaggregation for solution algorithms (LOA)
46+
_disaggregation_map(model)[(vref, lvref)] = dvref
4847
#create bounding constraints
4948
dvname = JuMP.name(dvref)
5049
lbname = isempty(dvname) ? "" : "$(dvname)_lower_bound"
@@ -257,9 +256,7 @@ function reformulate_disjunction(model::JuMP.AbstractModel, disj::Disjunction, m
257256
return ref_cons
258257
end
259258
function reformulate_disjunction(model::JuMP.AbstractModel, disj::Disjunction, method::_Hull)
260-
return reformulate_disjunction(model, disj,
261-
Hull(method.value;
262-
disaggregation_map = method.disaggregation_map))
259+
return reformulate_disjunction(model, disj, Hull(method.value))
263260
end
264261

265262
function reformulate_disjunct_constraint(

0 commit comments

Comments
 (0)