Skip to content

Commit

Permalink
Add support for vector-valued objective functions (#2070)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Feb 2, 2023
1 parent 954d8ca commit 38fc9e4
Show file tree
Hide file tree
Showing 16 changed files with 725 additions and 67 deletions.
1 change: 1 addition & 0 deletions docs/src/submodules/Bridges/list_of_bridges.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ These bridges are subtypes of [`Bridges.Objective.AbstractBridge`](@ref).
Bridges.Objective.FunctionizeBridge
Bridges.Objective.QuadratizeBridge
Bridges.Objective.SlackBridge
Bridges.Objective.VectorSlackBridge
```

## [Variable bridges](@id variable_bridges_ref)
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ function _general_bridge_tests(bridge::B) where {B<:AbstractBridge}
MOI.get(bridge, MOI.NumberOfVariables())
)
if B <: Objective.AbstractBridge
Test.@test set_objective_function_type(B) <: MOI.AbstractScalarFunction
Test.@test set_objective_function_type(B) <: MOI.AbstractFunction
end
return
end
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/Constraint/single_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ end

function MOI.Bridges.is_bridged(
::SingleBridgeOptimizer,
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOI.AbstractFunction},
)
return false
end
Expand Down
2 changes: 2 additions & 0 deletions src/Bridges/Objective/Objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include("single_bridge_optimizer.jl")
include("bridges/functionize.jl")
include("bridges/quadratize.jl")
include("bridges/slack.jl")
include("bridges/vector_slack.jl")

"""
add_all_bridges(model, ::Type{T}) where {T}
Expand All @@ -29,6 +30,7 @@ function add_all_bridges(model, ::Type{T}) where {T}
MOI.Bridges.add_bridge(model, FunctionizeBridge{T})
MOI.Bridges.add_bridge(model, QuadratizeBridge{T})
MOI.Bridges.add_bridge(model, SlackBridge{T})
MOI.Bridges.add_bridge(model, VectorSlackBridge{T})
return
end

Expand Down
14 changes: 7 additions & 7 deletions src/Bridges/Objective/bridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract type AbstractBridge <: MOI.Bridges.AbstractBridge end
"""
supports_objective_function(
BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
F::Type{<:MOI.AbstractScalarFunction},
F::Type{<:MOI.AbstractFunction},
)::Bool
Return a `Bool` indicating whether the bridges of type `BT` support bridging
Expand All @@ -37,15 +37,15 @@ objective functions of type `F`.
"""
function supports_objective_function(
::Type{<:AbstractBridge},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOI.AbstractFunction},
)
return false
end

"""
concrete_bridge_type(
BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
F::Type{<:MOI.AbstractScalarFunction},
F::Type{<:MOI.AbstractFunction},
)::Type
Return the concrete type of the bridge supporting objective functions of type
Expand All @@ -56,14 +56,14 @@ This function can only be called if `MOI.supports_objective_function(BT, F)` is
"""
function concrete_bridge_type(
::Type{BT},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOI.AbstractFunction},
) where {BT}
return BT
end

function concrete_bridge_type(
b::MOI.Bridges.AbstractBridgeOptimizer,
F::Type{<:MOI.AbstractScalarFunction},
F::Type{<:MOI.AbstractFunction},
)
return concrete_bridge_type(MOI.Bridges.bridge_type(b, F), F)
end
Expand All @@ -72,7 +72,7 @@ end
bridge_objective(
BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
model::MOI.ModelLike,
func::MOI.AbstractScalarFunction,
func::MOI.AbstractFunction,
)::BT
Bridge the objective function `func` using bridge `BT` to `model` and returns
Expand All @@ -86,7 +86,7 @@ a bridge object of type `BT`.
function bridge_objective(
::Type{<:AbstractBridge},
::MOI.ModelLike,
func::MOI.AbstractScalarFunction,
func::MOI.AbstractFunction,
)
return throw(
MOI.UnsupportedAttribute(MOI.ObjectiveFunction{typeof(func)}()),
Expand Down
199 changes: 199 additions & 0 deletions src/Bridges/Objective/bridges/vector_slack.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
VectorSlackBridge{T,F,G}
`VectorSlackBridge` implements the following reformulations:
* ``\\min\\{f(x)\\}`` into ``\\min\\{y\\;|\\; y - f(x) \\in \\mathbb{R}_+ \\}``
* ``\\max\\{f(x)\\}`` into ``\\max\\{y\\;|\\; f(x) - y \\in \\mathbb{R}_+ \\}``
where `F` is the type of `f(x) - y`, `G` is the type of `f(x)`, and `T` is the
coefficient type of `f(x)`.
## Source node
`VectorSlackBridge` supports:
* [`MOI.ObjectiveFunction{G}`](@ref)
## Target nodes
`VectorSlackBridge` creates:
* One variable node: [`MOI.VectorOfVariables`](@ref) in [`MOI.Reals`](@ref)
* One objective node: [`MOI.ObjectiveFunction{MOI.VectorOfVariables}`](@ref)
* One constraint node: `F`-in-[`MOI.Nonnegatives`](@ref)
!!! warning
When using this bridge, changing the optimization sense is not supported.
Set the sense to `MOI.FEASIBILITY_SENSE` first to delete the bridge, then
set [`MOI.ObjectiveSense`](@ref) and re-add the objective.
"""
struct VectorSlackBridge{
T,
F<:MOI.AbstractVectorFunction,
G<:MOI.AbstractVectorFunction,
} <: AbstractBridge
variables::Vector{MOI.VariableIndex}
slacked_objectives::Vector{Int}
constraint::MOI.ConstraintIndex{F,MOI.Nonnegatives}
end

const VectorSlack{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{VectorSlackBridge{T},OT}

function bridge_objective(
::Type{VectorSlackBridge{T,F,G}},
model::MOI.ModelLike,
func::G,
) where {T,F,G<:MOI.AbstractVectorFunction}
variables, slacked_objectives = MOI.VariableIndex[], Int[]
funcs = MOI.Utilities.eachscalar(func)
for (i, fi) in enumerate(funcs)
try
push!(variables, convert(MOI.VariableIndex, fi))
catch
push!(variables, MOI.add_variable(model))
push!(slacked_objectives, i)
end
end
MOI.set(
model,
MOI.ObjectiveFunction{MOI.VectorOfVariables}(),
MOI.VectorOfVariables(variables),
)
sense = MOI.get(model, MOI.ObjectiveSense())
if sense == MOI.FEASIBILITY_SENSE
error(
"Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when",
" using `MOI.Bridges.Objective.VectorSlackBridge`.",
)
end
slacks = MOI.VectorOfVariables(variables[slacked_objectives])
f = if sense == MOI.MIN_SENSE
MOI.Utilities.operate(-, T, slacks, funcs[slacked_objectives])
elseif sense == MOI.MAX_SENSE
MOI.Utilities.operate(-, T, funcs[slacked_objectives], slacks)
end
set = MOI.Nonnegatives(length(slacked_objectives))
ci = MOI.add_constraint(model, f, set)
return VectorSlackBridge{T,F,G}(variables, slacked_objectives, ci)
end

function supports_objective_function(
::Type{<:VectorSlackBridge{T}},
::Type{MOI.VectorOfVariables},
) where {T}
return false
end

function supports_objective_function(
::Type{<:VectorSlackBridge{T}},
::Type{F},
) where {T,F<:MOI.AbstractVectorFunction}
return MOI.Utilities.is_coefficient_type(F, T)
end

function MOI.Bridges.added_constrained_variable_types(
::Type{<:VectorSlackBridge},
)
return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(
::Type{<:VectorSlackBridge{T,F}},
) where {T,F}
return Tuple{Type,Type}[(F, MOI.Nonnegatives)]
end

function MOI.Bridges.set_objective_function_type(::Type{<:VectorSlackBridge})
return MOI.VectorOfVariables
end

function concrete_bridge_type(
::Type{<:VectorSlackBridge{T}},
G::Type{<:MOI.AbstractVectorFunction},
) where {T}
F = MOI.Utilities.promote_operation(-, T, G, MOI.VectorOfVariables)
return VectorSlackBridge{T,F,G}
end

function MOI.get(bridge::VectorSlackBridge, ::MOI.NumberOfVariables)::Int64
return length(bridge.slacked_objectives)
end

function MOI.get(bridge::VectorSlackBridge, ::MOI.ListOfVariableIndices)
return bridge.variables[bridge.slacked_objectives]
end

function MOI.get(
bridge::VectorSlackBridge{T,F},
::MOI.NumberOfConstraints{F,MOI.Nonnegatives},
)::Int64 where {T,F}
return 1
end

function MOI.get(
bridge::VectorSlackBridge{T,F},
::MOI.ListOfConstraintIndices{F,MOI.Nonnegatives},
) where {T,F}
return [bridge.constraint]
end

function MOI.delete(model::MOI.ModelLike, bridge::VectorSlackBridge)
MOI.delete(model, bridge.constraint)
MOI.delete(model, MOI.get(bridge, MOI.ListOfVariableIndices()))
return
end

function MOI.get(
model::MOI.ModelLike,
attr_g::MOI.Bridges.ObjectiveFunctionValue{G},
bridge::VectorSlackBridge{T,F,G},
) where {T,F,G}
N = attr_g.result_index
attr_f = MOI.Bridges.ObjectiveFunctionValue{MOI.VectorOfVariables}(N)
objective_value = MOI.get(model, attr_f)
con_primal = MOI.get(model, MOI.ConstraintPrimal(), bridge.constraint)
sense = MOI.get(model, MOI.ObjectiveSense())
if sense == MOI.MIN_SENSE
# con_primal = objective_value - f(x)
for (i, con_p) in zip(bridge.slacked_objectives, con_primal)
objective_value[i] -= con_p
end
else
@assert sense == MOI.MAX_SENSE
# con_primal = f(x) - objective_value
for (i, con_p) in zip(bridge.slacked_objectives, con_primal)
objective_value[i] += con_p
end
end
return objective_value
end

function MOI.get(
model::MOI.ModelLike,
::MOI.ObjectiveFunction{G},
bridge::VectorSlackBridge{T,F,G},
) where {T,F,G<:MOI.AbstractVectorFunction}
f = MOI.VectorOfVariables(bridge.variables)

con_f = MOI.get(model, MOI.ConstraintFunction(), bridge.constraint)
if MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE
# con_f = y - func so we need to negate it. Nothing to do in the
# MAX_SENSE case.
con_f = MOI.Utilities.operate(-, T, con_f)
end
con_fs = MOI.Utilities.eachscalar(con_f)
f_map = Dict(i => fi for (i, fi) in zip(bridge.slacked_objectives, con_fs))
args = [get(f_map, i, x) for (i, x) in enumerate(bridge.variables)]
g = MOI.Utilities.operate(vcat, T, args...)
slacks = bridge.variables[bridge.slacked_objectives]
g = MOI.Utilities.remove_variable(g, slacks)
return MOI.Utilities.convert_approx(G, g)
end
6 changes: 3 additions & 3 deletions src/Bridges/Objective/map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ bridging that type of objective function.
"""
mutable struct Map <: AbstractDict{MOI.ObjectiveFunction,AbstractBridge}
bridges::Dict{MOI.ObjectiveFunction,AbstractBridge}
function_type::Union{Nothing,Type{<:MOI.AbstractScalarFunction}}
function_type::Union{Nothing,Type{<:MOI.AbstractFunction}}
end

Map() = Map(Dict{MOI.ObjectiveFunction,AbstractBridge}(), nothing)
Expand Down Expand Up @@ -74,7 +74,7 @@ end
map::Map,
bridge::AbstractBridge,
::F,
) where {F<:MOI.AbstractScalarFunction}
) where {F<:MOI.AbstractFunction}
Stores the mapping `attr => bridge` where `attr` is
`MOI.ObjectiveFunction{F}()` and set [`function_type`](@ref) to `F`.
Expand All @@ -83,7 +83,7 @@ function add_key_for_bridge(
map::Map,
bridge::AbstractBridge,
::F,
) where {F<:MOI.AbstractScalarFunction}
) where {F<:MOI.AbstractFunction}
attr = MOI.ObjectiveFunction{F}()
map.function_type = F
map.bridges[attr] = bridge
Expand Down
6 changes: 3 additions & 3 deletions src/Bridges/Objective/single_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,21 @@ end

function MOI.Bridges.supports_bridging_objective_function(
::SingleBridgeOptimizer{BT},
F::Type{<:MOI.AbstractScalarFunction},
F::Type{<:MOI.AbstractFunction},
) where {BT}
return supports_objective_function(BT, F)
end

function MOI.Bridges.is_bridged(
bridge::SingleBridgeOptimizer,
F::Type{<:MOI.AbstractScalarFunction},
F::Type{<:MOI.AbstractFunction},
)
return MOI.Bridges.supports_bridging_objective_function(bridge, F)
end

function MOI.Bridges.bridge_type(
::SingleBridgeOptimizer{BT},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOI.AbstractFunction},
) where {BT}
return BT
end
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/bridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ function added_constraint_types end
"""
set_objective_function_type(
BT::Type{<:Objective.AbstractBridge},
)::Type{<:MOI.AbstractScalarFunction}
)::Type{<:MOI.AbstractFunction}
Return the type of objective function that bridges of concrete type `BT`
set.
Expand Down
Loading

0 comments on commit 38fc9e4

Please sign in to comment.