Skip to content

Commit

Permalink
Add bridge to that converts upper/lower constraints to interval const…
Browse files Browse the repository at this point in the history
…raints. Closes jump-dev#1193
  • Loading branch information
shadiakiki1986 committed Nov 11, 2020
1 parent b768681 commit d3f88a6
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 5 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, Inf)
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(-Inf, 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}
# }
43 changes: 38 additions & 5 deletions src/Test/UnitTests/modifications.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ end
modificationtests["solve_transform_singlevariable_lessthan"] = solve_transform_singlevariable_lessthan

"""
solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConfig)
solve_set_scalaraffine_lessthan2(model::MOI.ModelLike, config::TestConfig)
Test modifying set of ScalarAffineFunction-in-LessThan constraint. If
`config.solve=true` confirm that it solves correctly, and if
`config.duals=true`, check that the duals are computed correctly.
"""
function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConfig)
function solve_set_scalaraffine_lessthan2(model::MOI.ModelLike, config::TestConfig)
MOI.empty!(model)
MOIU.loadfromstring!(model,"""
variables: x
Expand All @@ -115,18 +115,19 @@ function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConfi
objective_value = 1.0,
variable_primal = [(x, 1.0)],
constraint_primal = [(c, 1.0)],
constraint_dual = [(c, -1.0)]
constraint_dual = [(c, 1.0)] # <<< difference from above
)
MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(2.0))
@test MOI.get(model, MOI.ConstraintSet(), c) == MOI.LessThan(2.0)
test_model_solution(model, config;
objective_value = 2.0,
variable_primal = [(x, 2.0)],
constraint_primal = [(c, 2.0)],
constraint_dual = [(c, -1.0)]
constraint_dual = [(c, 1.0)] # <<< difference from above
)
end
modificationtests["solve_set_scalaraffine_lessthan"] = solve_set_scalaraffine_lessthan
modificationtests["solve_set_scalaraffine_lessthan2"] = solve_set_scalaraffine_lessthan2


"""
solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConfig)
Expand Down Expand Up @@ -160,6 +161,38 @@ function solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConf
end
modificationtests["solve_coef_scalaraffine_lessthan"] = solve_coef_scalaraffine_lessthan

"""
solve_coef_scalaraffine_lessthan2(model::MOI.ModelLike, config::TestConfig)
Test modifying a variable coefficient in a ScalarAffineFunction-in-LessThan
constraint. If `config.solve=true` confirm that it solves correctly, and if
`config.duals=true`, check that the duals are computed correctly.
"""
function solve_coef_scalaraffine_lessthan2(model::MOI.ModelLike, config::TestConfig)
MOI.empty!(model)
MOIU.loadfromstring!(model,"""
variables: x
maxobjective: 1.0x
c: 1.0x <= 1.0
""")
x = MOI.get(model, MOI.VariableIndex, "x")
c = MOI.get(model, MOI.ConstraintIndex, "c")
test_model_solution(model, config;
objective_value = 1.0,
variable_primal = [(x, 1.0)],
constraint_primal = [(c, 1.0)],
constraint_dual = [(c, 1.0)] # <<< difference from above
)
MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 2.0))
test_model_solution(model, config;
objective_value = 0.5,
variable_primal = [(x, 0.5)],
constraint_primal = [(c, 1.0)],
constraint_dual = [(c, 0.5)] # <<< difference from above
)
end
modificationtests["solve_coef_scalaraffine_lessthan2"] = solve_coef_scalaraffine_lessthan2

"""
solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::TestConfig)
Expand Down
3 changes: 3 additions & 0 deletions test/Bridges/Constraint/flip_sign.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ include("../utilities.jl")
mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
config = MOIT.TestConfig()

const SAF = MOI.ScalarAffineFunction{Float64}
const GT = MOI.GreaterThan{Float64}

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

Expand Down
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 "LessToInterval2" 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_lessthan2(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_lessthan2(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

0 comments on commit d3f88a6

Please sign in to comment.