Skip to content
Closed
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
64 changes: 64 additions & 0 deletions ext/InfiniteDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,70 @@ function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef)
return !_is_parameter(vref)
end

# Bound info for parameter refs in disjunct constraints: parameter
# functions report their support extrema, finite parameters a point,
# other parameters their domain bounds. Returns nothing for
# variables.
function _parameter_bound_info(
vref::InfiniteOpt.GeneralVariableRef
)::Union{Nothing, Tuple{Float64, Float64}}
_is_parameter(vref) || return nothing
dvref = InfiniteOpt.dispatch_variable_ref(vref)
if dvref isa InfiniteOpt.ParameterFunctionRef
prefs = InfiniteOpt.parameter_list(dvref)
length(prefs) == 1 || return (-Inf, Inf)
supports = InfiniteOpt.supports(first(prefs))
isempty(supports) && return (-Inf, Inf)
func = InfiniteOpt.raw_function(dvref)
func_values = [func(s) for s in supports]
return (minimum(func_values), maximum(func_values))
elseif dvref isa InfiniteOpt.FiniteParameterRef
value = InfiniteOpt.parameter_value(dvref)
return (value, value)
end
lb = JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : -Inf
ub = JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : Inf
return (lb, ub)
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef, ::DP.BigM)
info = _parameter_bound_info(vref)
info === nothing || return info
lb = JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : -Inf
ub = JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : Inf
return lb, ub
end

# Hull and PSplit require finite bounds that include 0
function _zero_inclusive_bounds(
vref::InfiniteOpt.GeneralVariableRef,
info::Union{Nothing, Tuple{Float64, Float64}},
method_name::String
)::Tuple{Float64, Float64}
info === nothing || return (min(0, info[1]), max(0, info[2]))
if !JuMP.has_lower_bound(vref) || !JuMP.has_upper_bound(vref)
error("Variable $vref must have both lower and upper " *
"bounds defined when using the $method_name " *
"reformulation.")
end
return (min(0, JuMP.lower_bound(vref)),
max(0, JuMP.upper_bound(vref)))
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef, ::DP.Hull)
return _zero_inclusive_bounds(vref,
_parameter_bound_info(vref), "Hull")
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef,
::Union{DP.PSplit, DP._PSplit})
return _zero_inclusive_bounds(vref,
_parameter_bound_info(vref), "PSplit")
end

function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef)
info = DP.get_variable_info(vref)
name = JuMP.name(vref)
Expand Down
32 changes: 32 additions & 0 deletions test/extensions/InfiniteDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,37 @@ function test_requires_disaggregation()
@test DP.requires_disaggregation(y) == true
end

# Bound info for parameter refs in disjunct constraints: parameter
# functions report their support extrema, finite parameters a point,
# other parameters their domain bounds; Hull/PSplit clamp the bounds
# to include 0 and error when a variable is missing bounds.
function test_parameter_bound_info()
model = InfiniteGDPModel()
@infinite_parameter(model, t in [0, 1], supports = [0.0, 0.5, 1.0])
@infinite_parameter(model, s in [0, 1])
@finite_parameter(model, p == 2.0)
@variable(model, 0 <= x <= 10, Infinite(t))
@variable(model, y, Infinite(t))
@parameter_function(model, pf == t -> 2t - 1)
@parameter_function(model, pf2 == (t, s) -> t + s)
@parameter_function(model, pf3 == s -> s)
@test DP.set_variable_bound_info(pf, BigM()) == (-1.0, 1.0)
@test DP.set_variable_bound_info(p, BigM()) == (2.0, 2.0)
@test DP.set_variable_bound_info(t, BigM()) == (0.0, 1.0)
@test DP.set_variable_bound_info(x, BigM()) == (0.0, 10.0)
@test DP.set_variable_bound_info(y, BigM()) == (-Inf, Inf)
# multi-parameter and support-less parameter functions fall back
@test DP.set_variable_bound_info(pf2, BigM()) == (-Inf, Inf)
@test DP.set_variable_bound_info(pf3, BigM()) == (-Inf, Inf)
# Hull and PSplit clamp the bounds to include 0
@test DP.set_variable_bound_info(pf, Hull()) == (-1.0, 1.0)
@test DP.set_variable_bound_info(t, Hull()) == (0.0, 1.0)
@test DP.set_variable_bound_info(p, Hull()) == (0.0, 2.0)
@test DP.set_variable_bound_info(x, Hull()) == (0.0, 10.0)
@test DP.set_variable_bound_info(p, PSplit([[x]])) == (0.0, 2.0)
@test_throws ErrorException DP.set_variable_bound_info(y, Hull())
end

function test_all_variables_infiniteopt()
model = InfiniteGDPModel()
@infinite_parameter(model, t ∈ [0, 1])
Expand Down Expand Up @@ -806,6 +837,7 @@ end
test_is_parameter()
test_is_parameter_concrete_dispatches()
test_requires_disaggregation()
test_parameter_bound_info()
test_variable_properties_infiniteopt()
test_variable_properties_from_expr()
test_variable_properties_from_quad_expr()
Expand Down
Loading