diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index d3ab11b95b..547e5a3aeb 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -14,6 +14,8 @@ include("bridge_optimizer.jl") include("Variable/Variable.jl") # Constraint bridges include("Constraint/Constraint.jl") +# Objective bridges +include("Objective/Objective.jl") include("lazy_bridge_optimizer.jl") @@ -27,6 +29,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 diff --git a/src/Bridges/Objective/Objective.jl b/src/Bridges/Objective/Objective.jl new file mode 100644 index 0000000000..dfda55f132 --- /dev/null +++ b/src/Bridges/Objective/Objective.jl @@ -0,0 +1,31 @@ +module Objective + +using MathOptInterface +const MOI = MathOptInterface +const MOIB = MOI.Bridges + +# Definition of an objective bridge +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("zeros.jl") +#const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{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}) + return +end + +end diff --git a/src/Bridges/Objective/bridge.jl b/src/Bridges/Objective/bridge.jl new file mode 100644 index 0000000000..491d3e3034 --- /dev/null +++ b/src/Bridges/Objective/bridge.jl @@ -0,0 +1,109 @@ +""" + AbstractBridge + +Subtype of [`MathOptInterface.Bridges.AbstractBridge`](@ref) for objective +bridges. +""" +abstract type AbstractBridge <: MOIB.AbstractBridge end + +""" + bridge_objective(BT::Type{<:AbstractBridge}, model::MOI.ModelLike, + func::MOI.AbstractScalarFunction) + +Bridge the objective function `func` using bridge `BT` to `model` and returns +a bridge object of type `BT`. The bridge type `BT` should be a concrete type, +that is, all the type parameters of the bridge should be set. Use +[`concrete_bridge_type`](@ref) to obtain a concrete type for a given function +type. +""" +function bridge_objective end + +""" + function MOI.set(model::MOI.ModelLike, attr::MOI.ObjectiveSense, + bridge::AbstractBridge, sense::MOI.ObjectiveSense) + +Return the value of the attribute `attr` of the model `model` for the +variable bridged by `bridge`. +""" +function MOI.set(::MOI.ModelLike, attr::MOI.ObjectiveSense, + bridge::AbstractBridge, sense::MOI.ObjectiveSense) + throw(ArgumentError( + "Objective bridge of type `$(typeof(bridge))` does not support" * + " modifying the objective sense. As a workaround, set the sense to" * + " `MOI.FEASIBILITY_SENSE` to clear the objective function and" * + " bridges.")) +end + +""" + function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction, + bridge::AbstractBridge) + +Return the value of the objective function bridged by `bridge` for the model +`model`. +""" +function MOI.get(::MOI.ModelLike, ::MOI.ObjectiveFunction, + bridge::AbstractBridge) + throw(ArgumentError( + "ObjectiveFunction bridge of type `$(typeof(bridge))` does not" * + " support getting the objective function.")) +end + +""" + supports_objective_function( + BT::Type{<:AbstractBridge}, + F::Type{<:MOI.AbstractScalarFunction})::Bool + +Return a `Bool` indicating whether the bridges of type `BT` support bridging +objective functions of type `F`. +""" +function supports_objective_function( + ::Type{<:AbstractBridge}, ::Type{<:MOI.AbstractScalarFunction}) + return false +end + +""" + added_constrained_variable_types(BT::Type{<:MOI.Bridges.Objective.AbstractBridge}, + F::Type{<:MOI.AbstractScalarFunction}) + +Return a list of the types of constrained variables that bridges of type `BT` +add for bridging objective functions of type `F`. This fallbacks to +`added_constrained_variable_types(concrete_bridge_type(BT, F))` +so bridges should not implement this method. +``` +""" +function MOIB.added_constrained_variable_types( + BT::Type{<:AbstractBridge}, F::Type{<:MOI.AbstractScalarFunction}) + MOIB.added_constrained_variable_types(concrete_bridge_type(BT, F)) +end + +""" + added_constraint_types(BT::Type{<:MOI.Bridges.Objective.AbstractBridge}, + F::Type{<:MOI.AbstractScalarFunction}) + +Return a list of the types of constraints that bridges of type `BT` add +add for bridging objective functions of type `F`. This fallbacks to +`added_constraint_types(concrete_bridge_type(BT, S))` +so bridges should not implement this method. +""" +function MOIB.added_constraint_types( + BT::Type{<:AbstractBridge}, F::Type{<:MOI.AbstractScalarFunction}) + MOIB.added_constraint_types(concrete_bridge_type(BT, F)) +end + +""" + concrete_bridge_type(BT::Type{<:MOI.Bridges.Objective.AbstractBridge}, + F::Type{<:MOI.AbstractScalarFunction})::DataType + +Return the concrete type of the bridge supporting objective functions of type +`F`. This function can only be called if `MOI.supports_objective_function(BT, F)` +is `true`. +""" +function concrete_bridge_type(bridge_type::DataType, + ::Type{<:MOI.AbstractScalarFunction}) + return bridge_type +end + +function concrete_bridge_type(b::MOIB.AbstractBridgeOptimizer, + F::Type{<:MOI.AbstractScalarFunction}) + return concrete_bridge_type(MOIB.bridge_type(b, F), F) +end diff --git a/src/Bridges/Objective/functionize.jl b/src/Bridges/Objective/functionize.jl new file mode 100644 index 0000000000..25b0d934e4 --- /dev/null +++ b/src/Bridges/Objective/functionize.jl @@ -0,0 +1,40 @@ +""" + 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, + func::MOI.SingleVariable) where T + F = MOI.ScalarAffineFunction{T} + MOI.set(model, MOI.ObjectiveFunction{F}(), convert(F, func)) + return ScalarFunctionizeBridge{T}() +end + +function supports_objective_function( + ::Type{FunctionizeBridge{T}}, ::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 + +function MOI.delete(model::MOI.ModelLike, bridge::FunctionizeBridge) end + +function MOI.get(model::MOI.ModelLike, + attr::MOI.ObjectiveFunctionValue{MOI.SingleVariable}, + bridge::FunctionizeBridge) + F = MOI.ScalarAffineFunction{T} + return MOI.get(model, MOI.ObjectiveFunctionValue{F}()) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{MOI.SingleVariable}, + bridge::FunctionizeBridge) + F = MOI.ScalarAffineFunction{T} + func = MOI.get(model, MOI.ObjectiveFunction{F}()) + return convert(MOI.SingleVariable, func) +end diff --git a/src/Bridges/Objective/map.jl b/src/Bridges/Objective/map.jl new file mode 100644 index 0000000000..c488c0c9cc --- /dev/null +++ b/src/Bridges/Objective/map.jl @@ -0,0 +1,81 @@ +""" + Map <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} + +Mapping between bridged variables and the bridge that bridged the variable. +""" +mutable struct Map <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} + bridges::Dict{MOI.ObjectiveFunction, AbstractBridge} + function_type::Union{Nothing, Type{<:MOI.AbstractScalarFunction}} +end +function Map() + return Map(Dict{MOI.ObjectiveFunction, AbstractBridge}(), nothing) +end + +# Implementation of `AbstractDict` interface. + +Base.isempty(map::Map) = isempty(map.bridges) +function Base.empty!(map::Map) + empty!(map.bridges) + map.function_type = nothing +end +function Base.haskey(map::Map, attr::MOI.ObjectiveFunction) + return haskey(map.bridges, attr) +end +function Base.getindex(map::Map, attr::MOI.ObjectiveFunction) + return map.bridges[attr] +end +Base.length(map::Map) = length(map.bridges) +Base.values(map::Map) = values(map.bridges) +Base.iterate(map::Map, args...) = iterate(map.bridges, args...) + +# Custom interface for information needed by `AbstractBridgeOptimizer`s that is +# not part of the `AbstractDict` interface. + +""" + function_type(map::Map) + +Return the function type of the [`root_bridge`](@ref) or `nothing` if `map` is +empty. +""" +function_type(map::Map) = map.function_type + +""" + root_bridge(map::Map) + +Return the last bridge added. +""" +function root_bridge(map::Map) + attr = MOI.ObjectiveFunction{function_type(map)}() + return map[attr] +end + +""" + add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.AbstractScalarFunction) + +Stores the mapping `atttr => bridge` where `attr` is +`MOI.ObjectiveFunction{typeof(func)}()` and set [`function_type`](@ref) to +`typeof(func)`. +""" +function add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.AbstractScalarFunction) + attr = MOI.ObjectiveFunction{typeof(func)}() + map.function_type = typeof(func) + map.bridges[attr] = bridges +end + +""" + EmptyMap <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} + +Empty version of [`Map`](@ref). It is used by +[`MathOptInterface.Bridges.Constraint.SingleBridgeOptimizer`](@ref) as it does +not bridge any variable. +""" +struct EmptyMap <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} end +Base.isempty(::EmptyMap) = true +function Base.empty!(::EmptyMap) end +Base.length(::EmptyMap) = 0 +Base.haskey(::EmptyMap, ::MOI.ObjectiveFunction) = true +Base.values(::EmptyMap) = MOIB.EmptyVector{AbstractBridge}() +Base.iterate(::EmptyMap) = nothing +function_type(::EmptyMap) = nothing diff --git a/src/Bridges/Objective/single_bridge_optimizer.jl b/src/Bridges/Objective/single_bridge_optimizer.jl new file mode 100644 index 0000000000..7ad6ab75de --- /dev/null +++ b/src/Bridges/Objective/single_bridge_optimizer.jl @@ -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.AbstractSet}) where BT + return BT +end diff --git a/src/Bridges/bridge.jl b/src/Bridges/bridge.jl index 962c77dd1e..e81832562a 100644 --- a/src/Bridges/bridge.jl +++ b/src/Bridges/bridge.jl @@ -76,3 +76,11 @@ Return a list of the types of constraints that bridges of concrete type `BT` add. This is used by the [`LazyBridgeOptimizer`](@ref). """ function added_constraint_types end + +""" + set_objective_function_type(BT::Type{<:Objective.AbstractBridge})::Bool + +Return the type of objective function that bridges of concrete type `BT` +set. This is used by the [`LazyBridgeOptimizer`](@ref). +""" +function set_objective_function_type end diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 9f55ec2f43..0a3783cc17 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -28,6 +28,11 @@ instead of passing it as is to its internal model. Return a `Bool` indicating whether `b` tries to bridge constrained variables in `S` instead of passing it as is to its internal model. + + is_bridged(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction})::Bool + +Return a `Bool` indicating whether `b` tries to bridge objective functions of +type `F` instead of passing it as is to its internal model. """ function is_bridged end @@ -56,6 +61,23 @@ function is_bridged(b::AbstractBridgeOptimizer, return is_bridged(b, F, S) || ci.value < 0 end +""" + is_bridged(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveFunction) + +Return a `Bool` indicating whether `attr` is bridged. The objective function is +said to be bridged the objective function attribute passed to `b.model` is +different to `attr`. +""" +function is_bridged( + b::AbstractBridgeOptimizer, attr::MOI.ObjectiveFunction) + return haskey(Objective.bridges(b), attr) +end + +const ObjectiveAttribute = Union{ + MOI.ObjectiveSense, MOI.ObjectiveFunction, MOI.ObjectiveFunctionType +} + """ supports_bridging_constrained_variable( ::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractSet}) @@ -82,6 +104,19 @@ function supports_bridging_constraint( return false end +""" + supports_bridging_objective_function( + b::AbstractBridgeOptimizer, + F::Type{<:MOI.AbstractScalarFunction})::Bool + +Return a `Bool` indicating whether `b` supports bridging objective functions of +type `F`. +""" +function supports_bridging_objective_function( + ::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractScalarFunction}) + return false +end + """ bridge_type(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, @@ -135,6 +170,16 @@ function bridge(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex) end end +""" + bridge(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveFunction) + +Return the `Objective.AbstractBridge` used to bridge the objective function +`attr`. +""" +function bridge(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveFunction) + return Objective.bridges(b)[attr] +end + # Implementation of the MOI interface for AbstractBridgeOptimizer MOI.optimize!(b::AbstractBridgeOptimizer) = MOI.optimize!(b.model) @@ -445,6 +490,121 @@ function MOI.set(b::AbstractBridgeOptimizer, return MOI.set(b.model, attr, bridged_function(b, value)) end +# Objective + +""" + is_objective_bridged(b::AbstractBridgeOptimizer) + +Return a `Bool` indicating whether the objective is bridged. The objective is +said to be bridged if the value of `MOI.ObjectiveFunctionType` is different for +`b` and `b.model`. +""" +is_objective_bridged(b) = !isempty(Objective.bridges(b)) +function _delete_objective_bridges(b) + MOI.delete(b, Objective.root_bridge(Objective.bridges(b))) + empty!(Objective.bridges(b)) +end + +function MOI.supports(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveFunction{F}) where F + if is_bridged(b, S) + return supports_bridging_objective_function(b, S) + else + return MOI.supports(b.model, attr) + end +end +struct ObjectiveFunctionValue{F<:MOI.AbstractScalarFunction} end +function MOI.get(b::AbstractBridgeOptimizer, + attr::ObjectiveFunctionValue{F}) where F + obj_attr = MOI.ObjectiveFunction{F}() + if is_bridged(b, obj_attr) + return MOI.get(b, attr, bridge(b, obj_attr)) + else + return MOI.get(b.model, MOI.ObjectiveValue()) + end +end +function MOI.get(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveValue) + if is_objective_bridged(b) + F = Objective.function_type(Objective.bridges(b)) + return MOI.get(b, ObjectiveFunctionValue{F}()) + else + return MOI.get(b.model, attr) + end +end +function MOI.get(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveFunctionType) + if is_objective_bridged(b) + return Objective.function_type(Objective.bridges(b)) + else + return MOI.get(b.model, attr) + end +end +function MOI.get(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveSense) + return MOI.get(b.model, attr) +end +function MOI.get(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveFunction) + value = if is_bridged(b, attr) + MOI.get(b, attr, bridge(b, attr)) + else + MOI.get(b.model, attr) + end + return unbridged_function(value) +end +function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveSense, value) + if is_objective_bridged(b) + if sense == MOI.FEASIBILITY_SENSE + _delete_objective_bridges(b) + else + for bridge in values(Objective.bridges(b)) + MOI.set(b, attr, bridge) + end + end + end + MOI.set(b.model, attr, value) +end +function _bridge_objective(b, BridgeType, func) + bridge = Objective.bridge_objective(BridgeType, b, func) + Objective.add_key_for_bridge(Objective.bridges(b), bridge, func) +end +function MOI.set(b::AbstractBridgeOptimizer, + attr::MOI.ObjectiveFunction, + func::MOI.AbstractScalarFunction) + if is_objective_bridged(b) + _delete_objective_bridges(b) + end + if Variable.has_bridges(Variable.bridges(b)) + if func isa MOI.SingleVariable + if is_bridged(b, f.variable) + BridgeType = Objective.concrete_bridge_type( + Objective.FunctionizeBridge{Float64}, attr) + _bridge_objective(b, BridgeType, func) + end + else + func = bridged_function(b, func)::typeof(func) + end + end + if is_bridged(b, typeof(func)) + BridgeType = Objective.concrete_bridge_type(b, attr) + _bridge_objective(b, BridgeType, func) + else + MOI.set(b.model, attr, func) + end +end +function MOI.modify(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, + change::MOI.AbstractFunctionModification) + if is_bridged(b, change) + modify_bridged_change(b, obj, change) + else + if is_bridged(b, obj) + MOI.modify(b, bridge(b, obj), change) + else + MOI.modify(b.model, obj, change) + end + end +end # Variable attributes function _index(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) @@ -761,16 +921,6 @@ function MOI.modify(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex, end end -# Objective -function MOI.modify(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, - change::MOI.AbstractFunctionModification) - if is_bridged(b, change) - modify_bridged_change(b, obj, change) - else - MOI.modify(b.model, obj, change) - end -end - # Variables function MOI.add_variable(b::AbstractBridgeOptimizer) if is_bridged(b, MOI.Reals) diff --git a/src/Bridges/lazy_bridge_optimizer.jl b/src/Bridges/lazy_bridge_optimizer.jl index d4d722cff0..9804217284 100644 --- a/src/Bridges/lazy_bridge_optimizer.jl +++ b/src/Bridges/lazy_bridge_optimizer.jl @@ -20,6 +20,8 @@ mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer constraint_map::Constraint.Map con_to_name::Dict{MOI.ConstraintIndex, String} name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} + # Bridged objective + objective_map::Objective.Map # Bellman-Ford variable_bridge_types::Vector{Any} # List of types of available bridges variable_dist::Dict{Tuple{DataType}, Int} # (S,) -> Number of bridges that need to be used for constrained variables in `S` @@ -45,6 +47,9 @@ end function Constraint.bridges(bridge::LazyBridgeOptimizer) return bridge.constraint_map end +function Objective.bridges(b::LazyBridgeOptimizer) + return bridge.objective_map +end variable_function_type(::Type{<:MOI.AbstractScalarSet}) = MOI.SingleVariable variable_function_type(::Type{<:MOI.AbstractVectorSet}) = MOI.VectorOfVariables diff --git a/test/Bridges/Objective/Objective.jl b/test/Bridges/Objective/Objective.jl new file mode 100644 index 0000000000..f2a99d8443 --- /dev/null +++ b/test/Bridges/Objective/Objective.jl @@ -0,0 +1,3 @@ +@testset "Map" begin + include("map.jl") +end diff --git a/test/Bridges/Objective/map.jl b/test/Bridges/Objective/map.jl new file mode 100644 index 0000000000..898b0f45ba --- /dev/null +++ b/test/Bridges/Objective/map.jl @@ -0,0 +1,29 @@ +using Test +using MathOptInterface +const MOI = MathOptInterface +const MOIB = MOI.Bridges + +struct ObjectiveDummyBridge <: MOIB.Objective.AbstractBridge + id::Int +end + +function test_empty(map) + @test isempty(map) + @test length(map) == 0 + @test isempty(values(map)) + @test iterate(map) === nothing + @test MOIB.Objective.function_type(map) === nothing +end + +map= MOIB.Objective.Map() +test_empty(map) + +empty!(map) +test_empty(map) + +@testset "EmptyMap" begin + map = MOIB.Objective.EmptyMap() + test_empty(map) + empty!(map) + test_empty(map) +end