Skip to content

Commit

Permalink
Merge pull request #789 from JuliaOpt/bl/objective_bridge
Browse files Browse the repository at this point in the history
Add Objective bridges
  • Loading branch information
blegat authored Sep 5, 2019
2 parents a3ee2fb + 98f5063 commit 8d971fb
Show file tree
Hide file tree
Showing 15 changed files with 898 additions and 56 deletions.
1 change: 1 addition & 0 deletions src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function full_bridge_optimizer(model::MOI.ModelLike, T::Type)
bridged_model = LazyBridgeOptimizer(model)
Variable.add_all_bridges(bridged_model, T)
Constraint.add_all_bridges(bridged_model, T)
Objective.add_all_bridges(bridged_model, T)
return bridged_model
end

Expand Down
3 changes: 3 additions & 0 deletions src/Bridges/Constraint/single_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ function MOIB.is_bridged(b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractFunctio
S::Type{<:MOI.AbstractSet})
return MOIB.supports_bridging_constraint(b, F, S)
end
function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractScalarFunction})
return false
end
function MOIB.bridge_type(::SingleBridgeOptimizer{BT},
::Type{<:MOI.AbstractFunction},
::Type{<:MOI.AbstractSet}) where BT
Expand Down
21 changes: 21 additions & 0 deletions src/Bridges/Objective/Objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,25 @@ include("bridge.jl")
# Mapping between objective function attributes and bridges
include("map.jl")

# Bridge optimizer bridging a specific objective bridge
include("single_bridge_optimizer.jl")

# Objective bridges
include("functionize.jl")
const Functionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{FunctionizeBridge{T}, OT}
include("slack.jl")
const Slack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SlackBridge{T}, OT}

"""
add_all_bridges(bridged_model, T::Type)
Add all bridges defined in the `Bridges.Objective` submodule to `bridged_model`.
The coefficient type used is `T`.
"""
function add_all_bridges(bridged_model, T::Type)
MOIB.add_bridge(bridged_model, FunctionizeBridge{T})
MOIB.add_bridge(bridged_model, SlackBridge{T})
return
end

end
55 changes: 55 additions & 0 deletions src/Bridges/Objective/functionize.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
FunctionizeBridge{T}
The `FunctionizeBridge` converts a `SingleVariable` objective into a
`ScalarAffineFunction{T}` objective.
"""
struct FunctionizeBridge{T} <: AbstractBridge end
function bridge_objective(::Type{FunctionizeBridge{T}}, model::MOI.ModelLike,
func::MOI.SingleVariable) where T
F = MOI.ScalarAffineFunction{T}
MOI.set(model, MOI.ObjectiveFunction{F}(), convert(F, func))
return FunctionizeBridge{T}()
end

function supports_objective_function(
::Type{<:FunctionizeBridge}, ::Type{MOI.SingleVariable})
return true
end
MOIB.added_constrained_variable_types(::Type{<:FunctionizeBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{<:FunctionizeBridge})
return Tuple{DataType, DataType}[]
end
function MOIB.set_objective_function_type(::Type{FunctionizeBridge{T}}) where T
return MOI.ScalarAffineFunction{T}
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::FunctionizeBridge, ::MOI.NumberOfVariables)
return 0
end
function MOI.get(bridge::FunctionizeBridge, ::MOI.ListOfVariableIndices)
return MOI.VariableIndex[]
end

# No variables or constraints are created in this bridge so there is nothing to
# delete.
function MOI.delete(model::MOI.ModelLike, bridge::FunctionizeBridge) end

function MOI.set(::MOI.ModelLike, ::MOI.ObjectiveSense,
::FunctionizeBridge, ::MOI.OptimizationSense)
# `FunctionizeBridge` is sense agnostic, therefore, we don't need to change
# anything.
end
function MOI.get(model::MOI.ModelLike,
attr::MOIB.ObjectiveFunctionValue{MOI.SingleVariable},
bridge::FunctionizeBridge{T}) where T
F = MOI.ScalarAffineFunction{T}
return MOI.get(model, MOIB.ObjectiveFunctionValue{F}())
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{MOI.SingleVariable},
bridge::FunctionizeBridge{T}) where T
F = MOI.ScalarAffineFunction{T}
func = MOI.get(model, MOI.ObjectiveFunction{F}())
return convert(MOI.SingleVariable, func)
end
42 changes: 42 additions & 0 deletions src/Bridges/Objective/single_bridge_optimizer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer
The `SingleBridgeOptimizer` bridges any objective functions supported by the
bridge `BT`. This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref)
which only bridges the objective functions that are unsupported by the internal model,
even if they are supported by one of its bridges.
"""
mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer
model::OT
map::Map # `MOI.ObjectiveFunction` -> objective bridge
end
function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike}
SingleBridgeOptimizer{BT, OT}(model, Map())
end

function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer)
return EmptyMap()
end
bridges(bridge::SingleBridgeOptimizer) = bridge.map

MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = false
function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet})
return false
end
function MOIB.is_bridged(::SingleBridgeOptimizer,
::Type{<:MOI.AbstractFunction},
::Type{<:MOI.AbstractSet})
return false
end
function MOIB.supports_bridging_objective_function(
::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractScalarFunction}) where BT
return supports_objective_function(BT, F)
end
function MOIB.is_bridged(
b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction})
return MOIB.supports_bridging_objective_function(b, F)
end
function MOIB.bridge_type(::SingleBridgeOptimizer{BT},
::Type{<:MOI.AbstractScalarFunction}) where BT
return BT
end
95 changes: 95 additions & 0 deletions src/Bridges/Objective/slack.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
SlackBridge{T, F, G}
The `SlackBridge` converts an objective function of type `G` into a
[`SingleVariable`](@ref) objective by creating a slack variable and a
`F`-in-[`LessThan`](@ref) constraint for minimization or
`F`-in-[`LessThan`](@ref) constraint for maximization where `F` is
`MOI.Utilities.promote_operation(-, T, G, MOI.SingleVariable}`.
Note that when using this bridge, changing the optimization sense
is not supported. Set the sense to `MOI.FEASIBILITY_SENSE` first
to delete the bridge in order to change the sense, then re-add the objective.
"""
struct SlackBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: AbstractBridge
slack::MOI.VariableIndex
constraint::Union{MOI.ConstraintIndex{F, MOI.LessThan{T}},
MOI.ConstraintIndex{F, MOI.GreaterThan{T}}}
end
function bridge_objective(::Type{SlackBridge{T, F, G}}, model::MOI.ModelLike,
func::G) where {T, F, G<:MOI.AbstractScalarFunction}
slack = MOI.add_variable(model)
fslack = MOI.SingleVariable(slack)
f = MOIU.operate(-, T, func, fslack)
if MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE
set = MOI.LessThan(zero(T))
elseif MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
set = MOI.GreaterThan(zero(T))
else
error("Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when",
" using `MOI.Bridges.Objective.SlackBridge`.")
end
constraint = MOIU.normalize_and_add_constraint(model, f, set)
MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), fslack)
return SlackBridge{T, F, G}(slack, constraint)
end

function supports_objective_function(
::Type{<:SlackBridge}, ::Type{MOI.SingleVariable})
return false
end
function supports_objective_function(
::Type{<:SlackBridge}, ::Type{<:MOI.AbstractScalarFunction})
return true
end
MOIB.added_constrained_variable_types(::Type{<:SlackBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{<:SlackBridge{T, F}}) where {T, F}
return [(F, MOI.GreaterThan{T}), (F, MOI.LessThan{T})]
end
function MOIB.set_objective_function_type(::Type{<:SlackBridge})
return MOI.SingleVariable
end
function concrete_bridge_type(::Type{<:SlackBridge{T}},
G::Type{<:MOI.AbstractScalarFunction}) where T
F = MOIU.promote_operation(-, T, G, MOI.SingleVariable)
return SlackBridge{T, F, G}
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::SlackBridge, ::MOI.NumberOfVariables)
return 1
end
function MOI.get(bridge::SlackBridge, ::MOI.ListOfVariableIndices)
return [bridge.slack]
end
function MOI.get(bridge::SlackBridge{T, F}, ::MOI.NumberOfConstraints{F, S}) where {
T, F, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}}}
return bridge.constraint isa MOI.ConstraintIndex{F, S} ? 1 : 0
end
function MOI.get(bridge::SlackBridge{T, F}, ::MOI.ListOfConstraintIndices{F, S}) where {
T, F, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}}}
if bridge.constraint isa MOI.ConstraintIndex{F, S}
return [bridge.constraint]
else
return MOI.ConstraintIndex{F, S}[]
end
end

function MOI.delete(model::MOI.ModelLike, bridge::SlackBridge)
MOI.delete(model, bridge.constraint)
MOI.delete(model, bridge.slack)
end

function MOI.get(model::MOI.ModelLike,
attr::MOIB.ObjectiveFunctionValue{G},
bridge::SlackBridge{T, F, G}) where {T, F, G}
slack = MOI.get(model, MOIB.ObjectiveFunctionValue{MOI.SingleVariable}())
obj_slack_constant = MOI.get(model, MOI.ConstraintPrimal(), bridge.constraint)
# The constant was moved to the set as it is a scalar constraint.
constant = MOI.constant(MOI.get(model, MOI.ConstraintSet(), bridge.constraint))
return obj_slack_constant + slack + constant
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{G},
bridge::SlackBridge{T, F, G}) where {T, F, G<:MOI.AbstractScalarFunction}
func = MOI.get(model, MOI.ConstraintFunction(), bridge.constraint)
return MOIU.convert_approx(G, MOIU.remove_variable(func, bridge.slack))
end
3 changes: 3 additions & 0 deletions src/Bridges/Variable/single_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function MOIB.is_bridged(::SingleBridgeOptimizer,
::Type{<:MOI.AbstractSet})
return false
end
function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractScalarFunction})
return false
end
function MOIB.bridge_type(::SingleBridgeOptimizer{BT},
::Type{<:MOI.AbstractSet}) where BT
return BT
Expand Down
Loading

0 comments on commit 8d971fb

Please sign in to comment.