Skip to content
Merged
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
255 changes: 211 additions & 44 deletions src/Bridges/Constraint/interval.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
_lower_set(set::MOI.Interval) = MOI.GreaterThan(set.lower)
function _lower_set(set::MOI.Interval{T}) where {T<:AbstractFloat}
if set.lower == typemin(T)
return nothing
else
return MOI.GreaterThan(set.lower)
end
end
_upper_set(set::MOI.Interval) = MOI.LessThan(set.upper)
function _upper_set(set::MOI.Interval{T}) where {T<:AbstractFloat}
if set.upper == typemax(T)
return nothing
else
return MOI.LessThan(set.upper)
end
end
_lower_set(set::MOI.EqualTo) = MOI.GreaterThan(set.value)
_upper_set(set::MOI.EqualTo) = MOI.LessThan(set.value)
_lower_set(set::MOI.Zeros) = MOI.Nonnegatives(set.dimension)
Expand All @@ -17,16 +31,31 @@ a `F`-in-`US` constraint where we have either:
For instance, if `F` is `MOI.ScalarAffineFunction` and `S` is `MOI.Interval`,
it transforms the constraint ``l ≤ ⟨a, x⟩ + α ≤ u`` into the constraints
``⟨a, x⟩ + α ≥ l`` and ``⟨a, x⟩ + α ≤ u``.

!!! note
If `T<:AbstractFloat` and `S` is `MOI.Interval{T}` then no lower (resp.
upper) bound constraint is created if the lower (resp. upper) bound is
`typemin(T)` (resp. `typemax(T)`). Similarly, when
[`MathOptInterface.ConstraintSet`](@ref) is set, a lower or upper bound
constraint may be deleted or created accordingly.
"""
struct SplitIntervalBridge{
mutable struct SplitIntervalBridge{
T,
F<:MOI.AbstractFunction,
S<:MOI.AbstractSet,
LS<:MOI.AbstractSet,
US<:MOI.AbstractSet,
} <: AbstractBridge
lower::MOI.ConstraintIndex{F,LS}
upper::MOI.ConstraintIndex{F,US}
lower::Union{Nothing,MOI.ConstraintIndex{F,LS}}
upper::Union{Nothing,MOI.ConstraintIndex{F,US}}
# To allow the user to do
# ```jl
# x = MOI.add_variable(model)
# c = MOI.add_constraint(model, x, MOI.Interval(-Inf, Inf))
# MOI.set(model, MOI.ConstraintSet(), c, MOI.Interval(0.0, Inf))
# ```
# we need to store the function to create the lower bound constraint.
func::Union{Nothing,F}
end

function bridge_constraint(
Expand All @@ -35,9 +64,24 @@ function bridge_constraint(
f::F,
set::S,
) where {T,F,S,LS,US}
lower = MOI.add_constraint(model, f, _lower_set(set))
upper = MOI.add_constraint(model, f, _upper_set(set))
return SplitIntervalBridge{T,F,S,LS,US}(lower, upper)
lower_set = _lower_set(set)
if lower_set === nothing
lower = nothing
else
lower = MOI.add_constraint(model, f, _lower_set(set))
end
upper_set = _upper_set(set)
if upper_set === nothing
upper = nothing
else
upper = MOI.add_constraint(model, f, _upper_set(set))
end
if lower === nothing && upper === nothing
func = f
else
func = nothing
end
return SplitIntervalBridge{T,F,S,LS,US}(lower, upper, func)
end

function MOI.supports_constraint(
Expand Down Expand Up @@ -85,36 +129,56 @@ function concrete_bridge_type(
end

function MOI.get(
::SplitIntervalBridge{T,F,S,LS},
bridge::SplitIntervalBridge{T,F,S,LS},
::MOI.NumberOfConstraints{F,LS},
)::Int64 where {T,F,S,LS}
return 1
if bridge.lower === nothing
return 0
else
return 1
end
end

function MOI.get(
::SplitIntervalBridge{T,F,S,LS,US},
bridge::SplitIntervalBridge{T,F,S,LS,US},
::MOI.NumberOfConstraints{F,US},
)::Int64 where {T,F,S,LS,US}
return 1
if bridge.upper === nothing
return 0
else
return 1
end
end

function MOI.get(
bridge::SplitIntervalBridge{T,F,S,LS},
::MOI.ListOfConstraintIndices{F,LS},
) where {T,F,S,LS}
return [bridge.lower]
if bridge.lower === nothing
return MOI.ConstraintIndex{F,LS}[]
else
return [bridge.lower]
end
end

function MOI.get(
bridge::SplitIntervalBridge{T,F,S,LS,US},
::MOI.ListOfConstraintIndices{F,US},
) where {T,F,S,LS,US}
return [bridge.upper]
if bridge.upper === nothing
return MOI.ConstraintIndex{F,US}[]
else
return [bridge.upper]
end
end

function MOI.delete(model::MOI.ModelLike, bridge::SplitIntervalBridge)
MOI.delete(model, bridge.lower)
MOI.delete(model, bridge.upper)
if bridge.lower !== nothing
MOI.delete(model, bridge.lower)
end
if bridge.upper !== nothing
MOI.delete(model, bridge.upper)
end
return
end

Expand All @@ -128,13 +192,25 @@ function MOI.supports(
return MOI.supports(model, attr, ci_1) && MOI.supports(model, attr, ci_2)
end

function _error_double_inf(attr)
return error(
"Cannot get `$attr` for a constraint in the interval `[-Inf, Inf]`.",
)
end

function MOI.get(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimal,MOI.ConstraintPrimalStart},
bridge::SplitIntervalBridge,
)
# lower and upper should give the same value
return MOI.get(model, attr, bridge.lower)
if bridge.lower !== nothing
return MOI.get(model, attr, bridge.lower)
end
if bridge.upper !== nothing
return MOI.get(model, attr, bridge.upper)
end
return _error_double_inf(attr)
end

function MOI.set(
Expand All @@ -143,8 +219,12 @@ function MOI.set(
bridge::SplitIntervalBridge,
value,
)
MOI.set(model, attr, bridge.lower, value)
MOI.set(model, attr, bridge.upper, value)
if bridge.lower !== nothing
MOI.set(model, attr, bridge.lower, value)
end
if bridge.upper !== nothing
MOI.set(model, attr, bridge.upper, value)
end
return
end

Expand All @@ -157,10 +237,22 @@ end
function MOI.get(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintDual,MOI.ConstraintDualStart},
bridge::SplitIntervalBridge,
)
return MOI.get(model, attr, bridge.lower) +
MOI.get(model, attr, bridge.upper)
bridge::SplitIntervalBridge{T},
) where {T}
if bridge.lower === nothing
if bridge.upper === nothing
return zero(T)
else
return MOI.get(model, attr, bridge.upper)
end
else
if bridge.upper === nothing
return MOI.get(model, attr, bridge.lower)
else
return MOI.get(model, attr, bridge.lower) +
MOI.get(model, attr, bridge.upper)
end
end
end

function _split_dual_start(value)
Expand All @@ -185,36 +277,63 @@ function MOI.set(
bridge::SplitIntervalBridge{T},
value,
) where {T}
lower, upper = _split_dual_start(value)
MOI.set(model, attr, bridge.lower, lower)
MOI.set(model, attr, bridge.upper, upper)
if bridge.lower === nothing
if bridge.upper !== nothing
MOI.set(model, attr, bridge.upper, value)
end
else
if bridge.upper === nothing
MOI.set(model, attr, bridge.lower, value)
else
lower, upper = _split_dual_start(value)
MOI.set(model, attr, bridge.lower, lower)
MOI.set(model, attr, bridge.upper, upper)
end
end
return
end

function MOI.get(
model::MOI.ModelLike,
::MOI.ConstraintBasisStatus,
attr::MOI.ConstraintBasisStatus,
bridge::SplitIntervalBridge,
)
lower_stat = MOI.get(model, MOI.ConstraintBasisStatus(), bridge.lower)
if lower_stat == MOI.NONBASIC
return MOI.NONBASIC_AT_LOWER
if bridge.upper !== nothing
upper_stat = MOI.get(model, attr, bridge.upper)
if upper_stat == MOI.NONBASIC
return MOI.NONBASIC_AT_UPPER
end
end
upper_stat = MOI.get(model, MOI.ConstraintBasisStatus(), bridge.upper)
if upper_stat == MOI.NONBASIC
return MOI.NONBASIC_AT_UPPER
if bridge.lower === nothing
if bridge.upper === nothing
# The only case where the interval `[-∞, ∞]` is allowed is for
# `VariableIndex` constraints but `ConstraintBasisStatus` is not
# defined for `VariableIndex` constraints.
_error_double_inf(attr)
else
return upper_stat
end
else
lower_stat = MOI.get(model, attr, bridge.lower)
if lower_stat == MOI.NONBASIC
return MOI.NONBASIC_AT_LOWER
end
# Both statuses must be BASIC or SUPER_BASIC, so just return the lower.
return lower_stat
end
# Both statuses must be BASIC or SUPER_BASIC, so just return the lower.
return lower_stat
end

function MOI.modify(
model::MOI.ModelLike,
bridge::SplitIntervalBridge,
change::MOI.AbstractFunctionModification,
)
MOI.modify(model, bridge.lower, change)
MOI.modify(model, bridge.upper, change)
if bridge.lower !== nothing
MOI.modify(model, bridge.lower, change)
end
if bridge.upper !== nothing
MOI.modify(model, bridge.upper, change)
end
return
end

Expand All @@ -224,8 +343,12 @@ function MOI.set(
bridge::SplitIntervalBridge{T,F},
func::F,
) where {T,F}
MOI.set(model, MOI.ConstraintFunction(), bridge.lower, func)
MOI.set(model, MOI.ConstraintFunction(), bridge.upper, func)
if bridge.lower !== nothing
MOI.set(model, MOI.ConstraintFunction(), bridge.lower, func)
end
if bridge.upper !== nothing
MOI.set(model, MOI.ConstraintFunction(), bridge.upper, func)
end
return
end

Expand All @@ -235,8 +358,39 @@ function MOI.set(
bridge::SplitIntervalBridge{T,F,S},
change::S,
) where {T,F,S}
MOI.set(model, MOI.ConstraintSet(), bridge.lower, _lower_set(change))
MOI.set(model, MOI.ConstraintSet(), bridge.upper, _upper_set(change))
lower_set = _lower_set(change)
upper_set = _upper_set(change)
if lower_set === nothing && upper_set === nothing
# The constraints are going to be deleted, we store the function before
# it is lost.
bridge.func = MOI.get(model, MOI.ConstraintFunction(), bridge)
end
if bridge.lower === nothing
if lower_set !== nothing
func = MOI.get(model, MOI.ConstraintFunction(), bridge)
bridge.lower = MOI.add_constraint(model, func, lower_set)
end
else
if lower_set === nothing
MOI.delete(model, bridge.lower)
bridge.lower = nothing
else
MOI.set(model, MOI.ConstraintSet(), bridge.lower, lower_set)
end
end
if bridge.upper === nothing
if upper_set !== nothing
func = MOI.get(model, MOI.ConstraintFunction(), bridge)
bridge.upper = MOI.add_constraint(model, func, upper_set)
end
else
if upper_set === nothing
MOI.delete(model, bridge.upper)
bridge.upper = nothing
else
MOI.set(model, MOI.ConstraintSet(), bridge.upper, upper_set)
end
end
return
end

Expand All @@ -245,18 +399,31 @@ function MOI.get(
attr::MOI.ConstraintFunction,
bridge::SplitIntervalBridge,
)
return MOI.get(model, attr, bridge.lower)
if bridge.lower !== nothing
return MOI.get(model, attr, bridge.lower)
end
if bridge.upper !== nothing
return MOI.get(model, attr, bridge.upper)
end
return bridge.func
end

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintSet,
bridge::SplitIntervalBridge{T,F,MOI.Interval{T}},
) where {T,F}
return MOI.Interval(
MOI.get(model, attr, bridge.lower).lower,
MOI.get(model, attr, bridge.upper).upper,
)
if bridge.lower === nothing
lower = typemin(T)
else
lower = MOI.get(model, attr, bridge.lower).lower
end
if bridge.upper === nothing
upper = typemax(T)
else
upper = MOI.get(model, attr, bridge.upper).upper
end
return MOI.Interval(lower, upper)
end

function MOI.get(
Expand Down
4 changes: 2 additions & 2 deletions src/Bridges/Constraint/ltgt_to_interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ field by convention.

!!! warning
It is required that `T` be a `AbstractFloat` type because otherwise
typemin and typemax would either be not implemented (e.g. BigInt)
or would not give infinite value (e.g. Int). For this reason,
`typemin` and `typemax` would either be not implemented (e.g. `BigInt`)
or would not give infinite value (e.g. `Int`). For this reason,
this bridge is only added to
[`MathOptInterface.Bridges.full_bridge_optimizer`](@ref).

Expand Down
Loading