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
  • Loading branch information
shadiakiki1986 committed Nov 11, 2020
1 parent 6092c3d commit 5e56bc7
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
2 changes: 2 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ 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}
MOIB.add_bridge(bridged_model, GreaterToIntervalBridge{T})
MOIB.add_bridge(bridged_model, LessToIntervalBridge{T})
MOIB.add_bridge(bridged_model, GreaterToLessBridge{T})
MOIB.add_bridge(bridged_model, LessToGreaterBridge{T})
MOIB.add_bridge(bridged_model, NonnegToNonposBridge{T})
Expand Down
73 changes: 73 additions & 0 deletions src/Bridges/Constraint/ltgt_to_interval.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# The code here is mostly copied from the flip_sign.jl code for FlipSignBridge and GreaterToLessBridge

"""
AbstractToIntervalBridge{T, S1, F}
Bridge a `F`-in-`S1` constraint into an `F`-in-`Interval` constraint where we have either:
* `S1 = MOI.GreaterThan{T}`
* `S1 = MOI.LessThan{T}`
The `F`-in-`S1` constraint is stored in the `constraint`
field by convention.
"""
abstract type AbstractToIntervalBridge{
T, S1<:MOI.AbstractSet,
F<:MOI.AbstractFunction} <: 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(BT::Type{<:AbstractToIntervalBridge}, func) = func
adjoint_map_function(BT::Type{<:AbstractToIntervalBridge}, func) = func
inverse_adjoint_map_function(BT::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,
MOI.ScalarCoefficientChange(change.variable, change.new_coefficient))
end
function MOI.modify(model::MOI.ModelLike, bridge::AbstractToIntervalBridge,
change::MOI.MultirowChange{T}) where T
MOI.modify(model, bridge.constraint,
MOI.MultirowChange(change.variable,
change.new_coefficients))
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
8 changes: 8 additions & 0 deletions src/Bridges/Constraint/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,11 @@ 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}
# FIXME How to get this to work?
#const GreaterOrLessToInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{
# LessToIntervalBridge{T},
# SingleBridgeOptimizer{GreaterToIntervalBridge{T}, OT}
# }
130 changes: 130 additions & 0 deletions test/Bridges/Constraint/ltgt_to_interval.jl
Original file line number Diff line number Diff line change
@@ -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


# FIXME SEGFAULTS
#@testset "GreaterOrLessToInterval-unmocked" begin
# """
# Dummy optimizer that supports Interval only
# """
# module OnlyIntervalOptimizer
# using MathOptInterface
# const MOI = MathOptInterface
#
# 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
# end
#
# # model supports Interval but not LessThan or GreaterThan
# model = OnlyIntervalOptimizer.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 = GreaterToInterval{Float64}(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})
#end
9 changes: 6 additions & 3 deletions test/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -770,20 +770,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) == """
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 5e56bc7

Please sign in to comment.