Skip to content

Commit

Permalink
Add bridge that converts upper/lower constraints to interval constrai…
Browse files Browse the repository at this point in the history
…nts. Closes #1193 (#1195)

Co-authored-by: Benoît Legat <benoit.legat@gmail.com>
  • Loading branch information
shadiakiki1986 and blegat committed Nov 27, 2020
1 parent c424201 commit 6f16730
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -6,3 +6,4 @@ docs/build/
docs/site/
test/Benchmarks/*.json
Manifest.toml
*.swp
2 changes: 2 additions & 0 deletions docs/src/apireference.md
Expand Up @@ -799,6 +799,8 @@ Bridges.Constraint.AbstractBridge

Below is the list of constraint bridges implemented in this package.
```@docs
Bridges.Constraint.GreaterToIntervalBridge
Bridges.Constraint.LessToIntervalBridge
Bridges.Constraint.GreaterToLessBridge
Bridges.Constraint.LessToGreaterBridge
Bridges.Constraint.NonnegToNonposBridge
Expand Down
5 changes: 5 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Expand Up @@ -81,6 +81,11 @@ Add all bridges defined in the `Bridges.Constraint` submodule to
`bridged_model`. The coefficient type used is `T`.
"""
function add_all_bridges(bridged_model, ::Type{T}) where {T}
if T <: AbstractFloat
MOIB.add_bridge(bridged_model, GreaterToIntervalBridge{T})
MOIB.add_bridge(bridged_model, LessToIntervalBridge{T})
end

MOIB.add_bridge(bridged_model, GreaterToLessBridge{T})
MOIB.add_bridge(bridged_model, LessToGreaterBridge{T})
MOIB.add_bridge(bridged_model, NonnegToNonposBridge{T})
Expand Down
72 changes: 72 additions & 0 deletions src/Bridges/Constraint/ltgt_to_interval.jl
@@ -0,0 +1,72 @@
# The code here is mostly copied from the flip_sign.jl code for FlipSignBridge and GreaterToLessBridge

"""
AbstractToIntervalBridge{T, S1, F}
Bridge a `F`-in-`Interval` constraint into an `F`-in-`Interval{T}` constraint where we have either:
* `S1 = MOI.GreaterThan{T}`
* `S1 = MOI.LessThan{T}`
The `F`-in-`Interval{T}` constraint is stored in the `constraint`
field by convention.
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).
"""
abstract type AbstractToIntervalBridge{
T<:AbstractFloat, S1<:MOI.AbstractScalarSet,
F<:MOI.AbstractScalarFunction} <: SetMapBridge{T, MOI.Interval{T}, S1, F, F} end

# The function map is the identity. It is also an involution, symmetric, and a symmetric involution.
map_function(::Type{<:AbstractToIntervalBridge{T}}, func) where {T} = func
inverse_map_function(::Type{<:AbstractToIntervalBridge}, func) = func
adjoint_map_function(::Type{<:AbstractToIntervalBridge}, func) = func
inverse_adjoint_map_function(::Type{<:AbstractToIntervalBridge}, func) = func

# FIXME are these modify functions necessary?
function MOI.modify(model::MOI.ModelLike, bridge::AbstractToIntervalBridge,
change::MOI.ScalarCoefficientChange)
MOI.modify(model, bridge.constraint, change)
end
function MOI.modify(model::MOI.ModelLike, bridge::AbstractToIntervalBridge,
change::MOI.MultirowChange{T}) where T
MOI.modify(model, bridge.constraint, change)
end

"""
GreaterToIntervalBridge{T, F<:MOI.AbstractScalarFunction} <:
AbstractToIntervalBridge{T, MOI.GreaterThan{T}, F}
Transforms a `F`-in-`GreaterThan{T}` constraint into an `F`-in-`Interval{T}`
constraint.
"""
struct GreaterToIntervalBridge{T, F<:MOI.AbstractScalarFunction} <:
AbstractToIntervalBridge{T, MOI.GreaterThan{T}, F}
constraint::CI{F, MOI.Interval{T}}
end
map_set(::Type{<:GreaterToIntervalBridge}, set::MOI.GreaterThan) = MOI.Interval(set.lower, typemax(set.lower))
inverse_map_set(::Type{<:GreaterToIntervalBridge}, set::MOI.Interval) = MOI.GreaterThan(set.lower)
function concrete_bridge_type(::Type{<:GreaterToIntervalBridge{T}},
F::Type{<:MOI.AbstractScalarFunction},
::Type{MOI.GreaterThan{T}}) where T
return GreaterToIntervalBridge{T, F}
end

"""
LessToIntervalBridge{T, F<:MOI.AbstractScalarFunction} <:
AbstractToIntervalBridge{T, MOI.LessThan{T}, F}
Transforms a `F`-in-`LessThan{T}` constraint into an `F`-in-`Interval{T}`
constraint.
"""
struct LessToIntervalBridge{T, F<:MOI.AbstractScalarFunction} <:
AbstractToIntervalBridge{T, MOI.LessThan{T}, F}
constraint::CI{F, MOI.Interval{T}}
end
map_set(::Type{<:LessToIntervalBridge}, set::MOI.LessThan) = MOI.Interval(typemin(set.upper), set.upper)
inverse_map_set(::Type{<:LessToIntervalBridge}, set::MOI.Interval) = MOI.LessThan(set.upper)
function concrete_bridge_type(::Type{<:LessToIntervalBridge{T}},
F::Type{<:MOI.AbstractScalarFunction},
::Type{MOI.LessThan{T}}) where T
return LessToIntervalBridge{T, F}
end
3 changes: 3 additions & 0 deletions src/Bridges/Constraint/set_map.jl
Expand Up @@ -97,3 +97,6 @@ const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonne
include("rsoc.jl")
const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT}
const SOCR{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCRBridge{T}, OT}
include("ltgt_to_interval.jl")
const GreaterToInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GreaterToIntervalBridge{T}, OT}
const LessToInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LessToIntervalBridge{T}, OT}
130 changes: 130 additions & 0 deletions test/Bridges/Constraint/ltgt_to_interval.jl
@@ -0,0 +1,130 @@
# These tests are mostly copies of the flip_sign.jl tests for GreaterToLess

using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges

include("../utilities.jl")

mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
config = MOIT.TestConfig()

@testset "GreaterToInterval" begin
bridged_mock = MOIB.Constraint.GreaterToInterval{Float64}(mock)

MOIT.basic_constraint_tests(
bridged_mock, config,
include = [(F, S)
for F in [MOI.ScalarAffineFunction{Float64},
MOI.ScalarQuadraticFunction{Float64}]
for S in [MOI.GreaterThan{Float64}]])

MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, 0.0]),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0]))
MOIT.linear6test(bridged_mock, config)

ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()))

@testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()]
@test MOI.supports(bridged_mock, attr, typeof(ci))
MOI.set(bridged_mock, attr, ci, 2.0)
@test MOI.get(bridged_mock, attr, ci) 2.0
end

test_delete_bridge(bridged_mock, ci, 2,
((MOI.ScalarAffineFunction{Float64},
MOI.Interval{Float64}, 0),
))
end


@testset "LessToInterval" begin
bridged_mock = MOIB.Constraint.LessToInterval{Float64}(mock)

MOIT.basic_constraint_tests(
bridged_mock, config,
include = [(F, S)
for F in [MOI.SingleVariable, MOI.ScalarAffineFunction{Float64},
MOI.ScalarQuadraticFunction{Float64}]
for S in [MOI.LessThan{Float64}]])

MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0]),
MOI.FEASIBLE_POINT,
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1.0]
),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [2.0]),
MOI.FEASIBLE_POINT,
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1.0]
)
)
MOIT.solve_set_scalaraffine_lessthan(bridged_mock, config)

MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0]),
MOI.FEASIBLE_POINT,
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1.0]
),
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5]),
MOI.FEASIBLE_POINT,
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-0.5]
)
)
MOIT.solve_coef_scalaraffine_lessthan(bridged_mock, config)

ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()))

@testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()]
@test MOI.supports(bridged_mock, attr, typeof(ci))
MOI.set(bridged_mock, attr, ci, 2.0)
@test MOI.get(bridged_mock, attr, ci) 2.0
end

test_delete_bridge(bridged_mock, ci, 1,
((MOI.ScalarAffineFunction{Float64},
MOI.Interval{Float64}, 0),))
end


# Define a dummy optimizer that only supports intervals
# and use it in the below unmocked test
mutable struct Optimizer <: MOI.AbstractOptimizer
function Optimizer()
return new()
end
end

MOI.get(model::Optimizer, ::MOI.SolverName) = "OnlyIntervalOptimizer"

MOI.supports_constraint(::Optimizer, ::Type{MOI.ScalarAffineFunction{Float64}}, ::Type{MOI.Interval{Float64}}) = true

@testset "GreaterOrLessToInterval_unmocked" begin
# model supports Interval but not LessThan or GreaterThan
model = Optimizer()
@test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
@test !MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
@test !MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})

# bridged model supports all
bridged = MOIB.Constraint.GreaterToInterval{Float64}(MOIB.Constraint.LessToInterval{Float64}(model))
@test MOI.supports_constraint(bridged, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
@test MOI.supports_constraint(bridged, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
@test MOI.supports_constraint(bridged, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})

# bridged model with Bridges.full_bridge_optimizer
bridged2 = MOIB.full_bridge_optimizer(model, Float64)
@test MOI.supports_constraint(bridged2, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
@test MOI.supports_constraint(bridged2, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
@test MOI.supports_constraint(bridged2, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})

end
9 changes: 6 additions & 3 deletions test/Bridges/lazy_bridge_optimizer.jl
Expand Up @@ -768,20 +768,23 @@ end
@test !MOIB.is_variable_bridged(bridged, cy)
@test MOIB.bridge(bridged, cy) isa MOIB.Constraint.RSOCBridge{T}
@test sprint(MOIB.print_graph, bridged) == MOI.Utilities.replace_acronym("""
Bridge graph with 4 variable nodes, 9 constraint nodes and 0 objective nodes.
Bridge graph with 5 variable nodes, 11 constraint nodes and 0 objective nodes.
[1] constrained variables in `MOI.RotatedSecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (3).
[2] constrained variables in `MOI.PositiveSemidefiniteConeTriangle` are not supported
[3] constrained variables in `MOI.SecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (1).
[4] constrained variables in `MOI.Nonnegatives` are supported (distance 2) by adding free variables and then constrain them, see (6).
[5] constrained variables in `MOI.Interval{$T}` are supported (distance 3) by adding free variables and then constrain them, see (8).
(1) `MOI.VectorOfVariables`-in-`MOI.SecondOrderCone` constraints are bridged (distance 1) by $(MOIB.Constraint.VectorFunctionizeBridge{T,MOI.SecondOrderCone}).
(2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by $(MOIB.Constraint.RSOCBridge{T,MOI.VectorAffineFunction{T},MOI.VectorAffineFunction{T}}).
(3) `MOI.VectorOfVariables`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by $(MOIB.Constraint.RSOCBridge{T,MOI.VectorAffineFunction{T},MOI.VectorOfVariables}).
(4) `MOI.VectorAffineFunction{$T}`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
(5) `MOI.VectorOfVariables`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
(6) `MOI.VectorOfVariables`-in-`MOI.Nonnegatives` constraints are bridged (distance 1) by $(MOIB.Constraint.NonnegToNonposBridge{T,MOI.VectorAffineFunction{T},MOI.VectorOfVariables}).
(7) `MOI.SingleVariable`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.GreaterToLessBridge{T,MOI.ScalarAffineFunction{T},MOI.SingleVariable}).
(8) `MOI.SingleVariable`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.VectorizeBridge{T,MOI.VectorAffineFunction{T},MOI.Zeros,MOI.SingleVariable}).
(9) `MOI.SingleVariable`-in-`MOI.LessThan{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.LessToGreaterBridge{T,MOI.ScalarAffineFunction{T},MOI.SingleVariable}).
(8) `MOI.SingleVariable`-in-`MOI.Interval{$T}` constraints are bridged (distance 2) by $(MOIB.Constraint.ScalarFunctionizeBridge{T,MOI.Interval{T}}).
(9) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}}).
(10) `MOI.SingleVariable`-in-`MOI.LessThan{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.LessToGreaterBridge{T,MOI.ScalarAffineFunction{T},MOI.SingleVariable}).
(11) `MOI.SingleVariable`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.VectorizeBridge{T,MOI.VectorAffineFunction{T},MOI.Zeros,MOI.SingleVariable}).
""")
end

Expand Down

0 comments on commit 6f16730

Please sign in to comment.