Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.6.1"

[deps]
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"

[weakdeps]
Expand All @@ -16,18 +17,23 @@ InfiniteDisjunctiveProgramming = "InfiniteOpt"
[compat]
Aqua = "0.8"
JuMP = "1.18"
LinearAlgebra = "1"
Reexport = "1"
julia = "1.10"
Juniper = "0.9.3"
Ipopt = "1.9.0"
InfiniteOpt = "0.6.3"
Hypatia = "0.10"
Pajarito = "0.8"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84"
Hypatia = "b99e6be6-89ff-11e8-14f8-45c827f4f8f2"
Pajarito = "2f354839-79df-5901-9f0a-cdb2aac6fe30"

[targets]
test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt", "InfiniteOpt"]
test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt", "InfiniteOpt", "Hypatia", "Pajarito"]
2 changes: 2 additions & 0 deletions src/DisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Reexport.@reexport using JuMP

# Use Meta for metaprogramming
using Base.Meta
# Convexity checks for the exact quadratic hull reformulations
import LinearAlgebra
# Create aliases
import JuMP.MOI as _MOI
import JuMP.MOIU.CleverDicts as _MOIUC
Expand Down
42 changes: 42 additions & 0 deletions src/bigm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,45 @@ function reformulate_disjunct_constraint(
reform_con_np = JuMP.build_constraint(error, new_func_np, _MOI.Nonpositives(con.set.dimension))
return [reform_con_nn, reform_con_np]
end

################################################################################
# BIG-M FOR CONIC CONSTRAINTS
################################################################################
# Big-M for `func in K`: add slack `M*(1 - y)*d` along a fixed interior
# direction `d` of the cone, so `func + M*d in K`.

# SOC: (t, x...) with t >= ||x||. d = e1 = (1, 0, ...); interior since
# 1 > ||0|| = 0. Bumping t alone makes (t + M) >= ||x|| hold for big M.
_conic_bigm_direction(set::_MOI.SecondOrderCone) =
[i == 1 ? 1.0 : 0.0 for i in 1:_MOI.dimension(set)]
# Rotated SOC: (t, u, x...) with 2*t*u >= ||x||^2, t,u >= 0. d =
# (1,1,0,...); interior since 2*1*1 = 2 > 0. Both t,u must grow so
# 2(t+M)(u+M) ~ 2*M^2 dominates ||x||^2; bumping one leaves 2*t*0 = 0.
_conic_bigm_direction(set::_MOI.RotatedSecondOrderCone) =
[i <= 2 ? 1.0 : 0.0 for i in 1:_MOI.dimension(set)]
# Exp cone: (x, y, z) with z >= y*exp(x/y), y >= 0. d = (0, 1, 2);
# interior since 2 > 1*exp(0) = 1. As M grows, exp(x/(y+M)) -> 1 so the
# RHS ~ y + M ~ M while z grows as 2M; the factor 2 keeps z above it.
_conic_bigm_direction(::_MOI.ExponentialCone) = [0.0, 1.0, 2.0]
# Power cone: (x, y, z) with x^a * y^(1-a) >= |z|, x,y >= 0, a the cone
# exponent. d = (1,1,0); interior since 1^a * 1^(1-a) = 1 > 0. Bumping
# x,y makes (x+M)^a (y+M)^(1-a) ~ M dominate the fixed |z|; z untouched.
_conic_bigm_direction(::_MOI.PowerCone) = [1.0, 1.0, 0.0]

function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.VectorConstraint{T, S, R},
bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr},
method::BigM
) where {
T <: Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr},
S <: _ConicSets, R
}
M = method.value
d = _conic_bigm_direction(con.set)
new_func = JuMP.@expression(model, [i=1:_MOI.dimension(con.set)],
con.func[i] + M*(1 - bvref)*d[i]
)
reform_con = JuMP.build_constraint(error, new_func, con.set)
return [reform_con]
end
15 changes: 14 additions & 1 deletion src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,23 @@ function JuMP.build_constraint(
return _DisjunctConstraint(constr, tag.indicator)
end

# MOI conic sets handled by the BigM and Hull conic reformulations
# (Bernal Neira & Grossmann 2021). The affine map A*x - b sits inside
# the cone; the nonlinearity is carried by the cone itself.
const _ConicSets = Union{
_MOI.SecondOrderCone,
_MOI.RotatedSecondOrderCone,
_MOI.ExponentialCone,
_MOI.PowerCone,
}

# Allows for building DisjunctConstraints for VectorConstraints since these get parsed differently by JuMP (JuMP changes the set to a MOI.AbstractScalarSet)
for SetType in (
JuMP.Nonnegatives, JuMP.Nonpositives, JuMP.Zeros,
_MOI.Nonnegatives, _MOI.Nonpositives, _MOI.Zeros
_MOI.Nonnegatives, _MOI.Nonpositives, _MOI.Zeros,
JuMP.SecondOrderCone, JuMP.RotatedSecondOrderCone,
_MOI.SecondOrderCone, _MOI.RotatedSecondOrderCone,
_MOI.ExponentialCone, _MOI.PowerCone
)
@eval begin
@doc """
Expand Down
26 changes: 22 additions & 4 deletions src/datatypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -409,28 +409,46 @@ end
"""
Hull{T} <: AbstractReformulationMethod

A type for using the convex hull reformulation approach for disjunctive
A type for using the convex hull reformulation approach for disjunctive
constraints.

**Fields**
- `value::T`: epsilon value for nonlinear hull reformulations (default = `1e-6`).
- `quadratic::Symbol`: reformulation used for quadratic disjunct
constraints (default = `:epsilon`). Options are:
- `:epsilon`: ε-approximated perspective (Furman, Sawaya & Grossmann
2020).
- `:exact`: exact hull (Gusev & Bernal Neira 2025); routes each
constraint to CEHR when its quadratic part is convex and to GEHR
otherwise (equality constraints always use GEHR since they are
nonconvex).
- `:gehr`: always use the General Exact Hull Reformulation.
- `:cehr`: always use the Conic Exact Hull Reformulation (errors on
nonconvex quadratic constraints).
"""
struct Hull{T} <: AbstractReformulationMethod
value::T
function Hull(ϵ::T = 1e-6) where {T}
new{T}(ϵ)
quadratic::Symbol
function Hull(ϵ::T = 1e-6; quadratic::Symbol = :epsilon) where {T}
if !(quadratic in (:epsilon, :exact, :gehr, :cehr))
error("Invalid `quadratic` option `:$(quadratic)`. Choose " *
"from `:epsilon`, `:exact`, `:gehr`, or `:cehr`.")
end
new{T}(ϵ, quadratic)
end
end

# temp struct to store variable disaggregations (reset for each disjunction)
mutable struct _Hull{V <: JuMP.AbstractVariableRef, T} <: AbstractReformulationMethod
value::T
quadratic::Symbol
disjunction_variables::Dict{V, Vector{V}}
disjunct_variables::Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}
function _Hull(method::Hull{T}, vrefs::Set{V}) where {T, V <: JuMP.AbstractVariableRef}
new{V, T}(
method.value,
Dict{V, Vector{V}}(vref => V[] for vref in vrefs),
method.quadratic,
Dict{V, Vector{V}}(vref => V[] for vref in vrefs),
Dict{Tuple{V, Union{V, JuMP.GenericAffExpr{T, V}}}, V}()
)
end
Expand Down
Loading