Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f395569
inital split
dnguyen227 Mar 23, 2026
ed2eb87
.
dnguyen227 Mar 23, 2026
9e7211e
.
dnguyen227 Mar 23, 2026
616b7fa
.
dnguyen227 Mar 23, 2026
6f04419
inital split
dnguyen227 Mar 23, 2026
2a7b894
inital split
dnguyen227 Mar 23, 2026
eab521c
.
dnguyen227 Apr 20, 2026
a7ad3fd
.
dnguyen227 Apr 20, 2026
8fa5f3e
.
dnguyen227 Apr 21, 2026
ab3370b
.
dnguyen227 Apr 21, 2026
65e743f
.
dnguyen227 Apr 21, 2026
882390e
.
dnguyen227 Apr 21, 2026
778b598
.
dnguyen227 Apr 21, 2026
2c0071a
.
dnguyen227 Apr 21, 2026
a77b988
.
dnguyen227 Apr 22, 2026
1ec83f4
.
dnguyen227 Apr 22, 2026
f863f15
.
dnguyen227 Apr 22, 2026
350fda3
.
dnguyen227 Apr 24, 2026
5235294
.
dnguyen227 Apr 27, 2026
32ccde5
.
dnguyen227 Apr 27, 2026
bb03b3b
.
dnguyen227 Apr 27, 2026
f2387f5
.
dnguyen227 Apr 27, 2026
fbdafa0
.
dnguyen227 Apr 27, 2026
153c1e7
.
dnguyen227 Apr 27, 2026
d053c07
.
dnguyen227 Apr 27, 2026
b0184fc
Update to get_variable_info, copy_model_with_constraints, prepare_max…
dnguyen227 May 1, 2026
2909c84
Internal constant interpolation
dnguyen227 May 1, 2026
bb76999
Change to copy_model_with_constraints to use copy_model(::InfiniteModel)
dnguyen227 May 14, 2026
fb5f381
Project.toml revert
dnguyen227 May 14, 2026
e420f98
Using master version of InfiniteOpt
dnguyen227 May 14, 2026
c0ca8fe
Reverting CI change
dnguyen227 May 14, 2026
404c7d6
loa_two_models squashed
dnguyen227 Apr 22, 2026
025140b
.
dnguyen227 Apr 24, 2026
eb65da6
.
dnguyen227 Apr 25, 2026
3bf94fa
.
dnguyen227 Apr 27, 2026
b6563d7
.
dnguyen227 Apr 27, 2026
efff096
.
dnguyen227 Apr 27, 2026
76a55f8
prelim
dnguyen227 Apr 27, 2026
1edcb94
Removed NLP-feas problem
dnguyen227 May 14, 2026
9a9662f
Adding global constraints to cuts
dnguyen227 May 14, 2026
ca61183
Global slack bug fix
dnguyen227 May 15, 2026
cd9dd70
Update InfiniteOpt version to 0.6.2
dnguyen227 May 19, 2026
bb0db43
Add filter_constraints workflow
dnguyen227 May 24, 2026
34b3ea8
Bump InfiniteOpt version to 0.6.3
dnguyen227 May 25, 2026
01d36dc
.
dnguyen227 May 26, 2026
1aa2fe7
Rebase
dnguyen227 May 26, 2026
0ac6bd0
.
dnguyen227 May 26, 2026
54c2bc5
.
dnguyen227 May 26, 2026
23dede6
Intermediate
dnguyen227 May 29, 2026
af043e0
.
dnguyen227 Jun 11, 2026
f2dd804
.
dnguyen227 Jun 11, 2026
c674594
.
dnguyen227 Jun 15, 2026
3a8fa5b
Fix LOA BoundsError on equality cuts
dnguyen227 Jun 16, 2026
c1f4bd3
LOA termination message for nonconvex
dnguyen227 Jun 16, 2026
128491f
Refactor LOA, drop multi-resolution, add iteration and total time limits
dnguyen227 Jun 17, 2026
396b35c
Removing leftover checks
dnguyen227 Jun 21, 2026
6a9d6e0
Reduce functions in loa workflow
dnguyen227 Jun 28, 2026
9896a78
LOA Hull inner method
dnguyen227 Jun 30, 2026
c77f732
Docstrings and tests for LOA
dnguyen227 Jul 1, 2026
5317552
Reverting Project.toml
dnguyen227 Jul 1, 2026
c36dfef
Merge branch 'master' into loa_two_models
dnguyen227 Jul 1, 2026
7de44e7
fixing Project.toml
dnguyen227 Jul 2, 2026
ec73ec6
_restore_logical_binaries
dnguyen227 Jul 2, 2026
441a806
Refactor LOA to solve the transcribed backend directly
dnguyen227 Jul 2, 2026
70cda3d
Add LOA coverage tests
dnguyen227 Jul 2, 2026
025f9f8
Adding utilities tests
dnguyen227 Jul 2, 2026
5adee47
_is_linear test coverage
dnguyen227 Jul 3, 2026
5b9dca1
Infeasible NLP test
dnguyen227 Jul 3, 2026
1f38951
Option to forgo nlpf
dnguyen227 Jul 3, 2026
4c31ca6
bound T in _LOAProblem
dnguyen227 Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ A [`GDPModel`](@ref) is a `JuMP Model` with a [`GDPData`](@ref) field in the mod
- `Logical Constraints`: Selector (cardinality) or proposition (Boolean) constraints describing the relationships between the logical variables.
- `Disjunct Constraints`: Constraints associated with each disjunct in the model.
- `Disjunctions`: Disjunction constraints.
- `Solution Method`: The reformulation technique or solution method. Currently, supported methods include Big-M, Hull, and Indicator Constraints.
- `Solution Method`: The reformulation technique or solution method. Currently, supported methods include Big-M, Multiple Big-M, Hull, P-Split, Indicator Constraints, and Logic-based Outer Approximation (LOA).
- `Reformulation Variables`: List of JuMP variables created when reformulating a GDP model into a MIP model.
- `Reformulation Constraints`: List of constraints created when reformulating a GDP model into a MIP model.
- `Ready to Optimize`: Flag indicating if the model can be optimized.
Expand Down Expand Up @@ -163,9 +163,15 @@ The following reformulation methods are currently supported:

1. [Big-M](https://optimization.cbe.cornell.edu/index.php?title=Disjunctive_inequalities#Big-M_Reformulation[1][2]): The [`BigM`](@ref) struct is used.

2. [Hull](https://optimization.cbe.cornell.edu/index.php?title=Disjunctive_inequalities#Convex-Hull_Reformulation[1][2]): The [`Hull`](@ref) struct is used.
2. Multiple Big-M: A tighter big-M reformulation that computes a separate big-M value for each disjunct constraint (rather than a single global value) by solving auxiliary mini-models. This is invoked with the [`MBM`](@ref) struct, which requires an optimizer.

3. [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).
3. [Hull](https://optimization.cbe.cornell.edu/index.php?title=Disjunctive_inequalities#Convex-Hull_Reformulation[1][2]): The [`Hull`](@ref) struct is used.

4. P-Split: A partitioned reformulation that splits the variables into groups and applies a P-split formulation to each group, giving a relaxation between Big-M and Hull in tightness. This is invoked with the [`PSplit`](@ref) struct.

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).

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

## Release Notes

Expand Down
126 changes: 124 additions & 2 deletions ext/InfiniteDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ function _interpolate_at(
args::NTuple{N, <:Real}
) where {N}
# lower-corner cell index per dimension
idx_lo = ntuple(d ->
idx_lo = ntuple(d ->
clamp(searchsortedlast(grids[d], args[d]),1, length(grids[d]) - 1), N
)
# max over the 2^N corners; bit d of k picks lower or upper
Expand Down Expand Up @@ -324,7 +324,10 @@ function DP.copy_and_reformulate(
end
sub = DP.GDPSubmodel(sub_copy, decision_vars, fwd_map)
JuMP.set_optimizer(sub.model, method.optimizer)
JuMP.set_silent(sub.model)
# NOTE: previously called JuMP.set_silent(sub.model) here, but
# that hides the Gurobi B&B trace even when the caller passes
# OutputFlag=1 / LogToConsole=1 via optimizer_with_attributes.
# The caller is now responsible for silencing if desired.
return sub
end

Expand Down Expand Up @@ -374,4 +377,123 @@ function DP.add_cut(
return
end

################################################################################
# LOA FOR INFINITEMODEL
################################################################################
# LOA override for InfiniteModel: after the inner reformulation, build
# the transformation backend and hand the LOA loop the backend model —
# a plain scalar JuMP model whose variables, measures, and constraints
# are already transcribed per support. The loop runs entirely on base
# code and solves the backend directly, which never invalidates it, so
# the committed fixes and warm starts survive the final `optimize!`.

# Per-support transcribed refs of an InfiniteOpt object, as a Vector
# (finite objects give one element).
_transcribed_refs(x::AbstractArray) = vec(x)
_transcribed_refs(x) = [x]

# Index a per-support vector, broadcasting a single-element (finite)
# vector across all supports.
_at(values::Vector, k::Int) =
length(values) == 1 ? values[1] : values[k]

# Per-support transcribed binary references for an indicator: the
# transcribed underlying binary, complemented for a complement-form
# indicator.
function _transcribed_binary_refs(model::InfiniteOpt.InfiniteModel, indicator)
binary_ref = DP._indicator_to_binary(model)[indicator]
binaries = _transcribed_refs(InfiniteOpt.transformation_variable(
DP._underlying_binary(binary_ref)))
binary_ref isa JuMP.GenericAffExpr || return binaries
return [1.0 - binary for binary in binaries]
end

# Transcribe the inner Hull disaggregation map per support. A finite
# variable under an infinite indicator keys its single disaggregated
# copy by each per-support binary reference, mirroring the per-support
# disjunct cuts that look it up.
_transcribed_disaggregation_map(::InfiniteOpt.InfiniteModel, ::Nothing) =
nothing
function _transcribed_disaggregation_map(
model::InfiniteOpt.InfiniteModel,
disaggregation_map
)
result = Dict{Tuple{JuMP.VariableRef,
Union{JuMP.VariableRef, JuMP.AffExpr}}, JuMP.VariableRef}()
for ((variable, indicator), disaggregated) in disaggregation_map
binary_refs = _transcribed_binary_refs(model, indicator)
variables = _transcribed_refs(
InfiniteOpt.transformation_variable(variable))
disaggregateds = _transcribed_refs(InfiniteOpt.transformation_variable(
disaggregated))
n = max(length(binary_refs), length(variables), length(disaggregateds))
for k in 1:n
result[(_at(variables, k), _at(binary_refs, k))] =
_at(disaggregateds, k)
end
end
return result
end

function DP.build_loa_problem(
model::InfiniteOpt.InfiniteModel,
method::DP.LOA,
disaggregation_map = nothing
)
InfiniteOpt.build_transformation_backend!(model)
nlp = InfiniteOpt.transformation_model(model)

binaries = JuMP.VariableRef[]
for (_, binary_ref) in DP._indicator_to_binary(model)
append!(binaries, _transcribed_refs(
InfiniteOpt.transformation_variable(
DP._underlying_binary(binary_ref))))
end
unique!(binaries)

disjunct_constraints = Tuple{Union{JuMP.VariableRef, JuMP.AffExpr},
JuMP.AbstractJuMPScalar, _MOI.AbstractScalarSet}[]
for (_, disjunction) in DP._disjunctions(model)
for indicator in disjunction.constraint.indicators
haskey(DP._indicator_to_constraints(model), indicator) ||
continue
binary_refs = _transcribed_binary_refs(model, indicator)
for cref in DP._indicator_to_constraints(model)[indicator]
cref isa DP.DisjunctConstraintRef || continue
constraint = DP._disjunct_constraints(model)[
JuMP.index(cref)].constraint
constraint isa JuMP.ScalarConstraint || continue
DP._is_linear_F(typeof(constraint.func)) && continue
funcs = _transcribed_refs(InfiniteOpt.transformation_expression(
constraint.func))
for k in 1:max(length(binary_refs), length(funcs))
push!(disjunct_constraints,
(_at(binary_refs, k), _at(funcs, k), constraint.set))
end
end
end
end

global_constraints = Tuple{JuMP.AbstractJuMPScalar,
_MOI.AbstractScalarSet}[]
reform_set = Set(DP._reformulation_constraints(model))
for (F, S) in JuMP.list_of_constraint_types(model)
F === InfiniteOpt.GeneralVariableRef && continue
DP._is_linear_F(F) && continue
for cref in JuMP.all_constraints(model, F, S)
cref in reform_set && continue
constraint = JuMP.constraint_object(cref)
constraint isa JuMP.ScalarConstraint || continue
for func in _transcribed_refs(InfiniteOpt.transformation_expression(
constraint.func))
push!(global_constraints, (func, constraint.set))
end
end
end

return DP._LOAProblem(nlp, binaries,
disjunct_constraints, global_constraints,
_transcribed_disaggregation_map(model, disaggregation_map))
end

end
1 change: 1 addition & 0 deletions src/DisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ include("print.jl")
include("extension_api.jl")
include("utilities.jl")
include("psplit.jl")
include("loa.jl")

# Define additional stuff that should not be exported
const _EXCLUDE_SYMBOLS = [Symbol(@__MODULE__), :eval, :include]
Expand Down
20 changes: 6 additions & 14 deletions src/cuttingplanes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,17 @@ function collect_cutting_planes_vars(model::JuMP.AbstractModel)
return collect_all_vars(model)
end

# Extract solution from a solved model (in-place). Extensions
# override for models where values live on a backend.
# Read primal values from a solved model. Returns
# `Dict{var, Vector{value}}` — per-support shape uniformly: finite
# models trivially have one "support" (length-1 Vector), the
# InfiniteOpt extension overrides this dispatch to populate
# multi-support Vectors. Skips fixed vars.
function extract_solution(model::JuMP.AbstractModel)
dvars = collect_cutting_planes_vars(model)
V = eltype(dvars)
T = JuMP.value_type(typeof(model))
return Dict{V, Vector{T}}(
v => [JuMP.value(v)] for v in dvars)
end

# Extract solution from a GDPSubmodel (SEP path).
function extract_solution(sub::GDPSubmodel)
V = eltype(sub.decision_vars)
T = JuMP.value_type(typeof(sub.model))
sol = Dict{V, Vector{T}}()
for var in sub.decision_vars
sol[var] = JuMP.value.(sub.fwd_map[var])
end
return sol
v => [JuMP.value(v)] for v in dvars if !JuMP.is_fixed(v))
end

# Set quadratic separation objective: min Σ (x_k - rBM_k)².
Expand Down
Loading
Loading