diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index d2dd932107..a50989643d 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -13,6 +13,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") @@ -26,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 diff --git a/src/Bridges/Constraint/single_bridge_optimizer.jl b/src/Bridges/Constraint/single_bridge_optimizer.jl index 2730addf5f..5fe1700407 100644 --- a/src/Bridges/Constraint/single_bridge_optimizer.jl +++ b/src/Bridges/Constraint/single_bridge_optimizer.jl @@ -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 diff --git a/src/Bridges/Objective/Objective.jl b/src/Bridges/Objective/Objective.jl new file mode 100644 index 0000000000..ad61866b68 --- /dev/null +++ b/src/Bridges/Objective/Objective.jl @@ -0,0 +1,35 @@ +module Objective + +using MathOptInterface +const MOI = MathOptInterface +const MOIU = MOI.Utilities +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("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 diff --git a/src/Bridges/Objective/bridge.jl b/src/Bridges/Objective/bridge.jl new file mode 100644 index 0000000000..9e4da2e7d8 --- /dev/null +++ b/src/Bridges/Objective/bridge.jl @@ -0,0 +1,118 @@ +""" + 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(::Type{<:AbstractBridge}, ::MOI.ModelLike, + func::MOI.AbstractScalarFunction) + throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction{typeof(func)}())) +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, ::MOI.ObjectiveSense, + bridge::AbstractBridge, ::MOI.OptimizationSense) + 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}) + return 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}) + return MOIB.added_constraint_types(concrete_bridge_type(BT, F)) +end + +function MOIB.set_objective_function_type( + BT::Type{<:AbstractBridge}, F::Type{<:MOI.AbstractScalarFunction}) + return MOIB.set_objective_function_type(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..f5381ba63e --- /dev/null +++ b/src/Bridges/Objective/functionize.jl @@ -0,0 +1,43 @@ +""" + 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 + +function MOI.delete(model::MOI.ModelLike, bridge::FunctionizeBridge) end + +function MOI.set(::MOI.ModelLike, ::MOI.ObjectiveSense, + ::FunctionizeBridge, ::MOI.OptimizationSense) +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 diff --git a/src/Bridges/Objective/map.jl b/src/Bridges/Objective/map.jl new file mode 100644 index 0000000000..77a259b45c --- /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] = bridge +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) = false +Base.values(::EmptyMap) = MOIU.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..67c40b8594 --- /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.AbstractScalarFunction}) where BT + return BT +end diff --git a/src/Bridges/Objective/slack.jl b/src/Bridges/Objective/slack.jl new file mode 100644 index 0000000000..b4ec0f9fce --- /dev/null +++ b/src/Bridges/Objective/slack.jl @@ -0,0 +1,76 @@ +""" + 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 +in order to delete the bridge in order to change the sense. +""" +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.add_scalar_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 + + +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{MOI.SingleVariable}, + bridge::SlackBridge{T}) where T + func = MOI.get(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) + return MOIU.remove_variable(func, bridge.slack) +end diff --git a/src/Bridges/Variable/single_bridge_optimizer.jl b/src/Bridges/Variable/single_bridge_optimizer.jl index 035236753a..47aa24c6ee 100644 --- a/src/Bridges/Variable/single_bridge_optimizer.jl +++ b/src/Bridges/Variable/single_bridge_optimizer.jl @@ -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 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 2e7262dbda..c889809ac4 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}, @@ -134,6 +169,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) @@ -450,6 +495,123 @@ 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, F) + return supports_bridging_objective_function(b, F) + 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(b, value) +end +function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveSense, + value::MOI.OptimizationSense) + if is_objective_bridged(b) + if value == MOI.FEASIBILITY_SENSE + _delete_objective_bridges(b) + else + for bridge in values(Objective.bridges(b)) + MOI.set(b, attr, bridge, value) + 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, func.variable) + BridgeType = Objective.concrete_bridge_type( + Objective.FunctionizeBridge{Float64}, typeof(func)) + _bridge_objective(b, BridgeType, func) + return + end + else + func = bridged_function(b, func)::typeof(func) + end + end + if is_bridged(b, typeof(func)) + BridgeType = Objective.concrete_bridge_type(b, typeof(func)) + _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) @@ -670,6 +832,11 @@ end function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{MOI.ConstraintIndex{F, S}}, name::String) where {F, S} + if !Constraint.has_bridges(Constraint.bridges(b)) && + !Variable.has_bridges(Variable.bridges(b)) + # `name_to_con` is not defined for `Objective.SingleBridgeOptimizer`. + return MOI.get(b.model, IdxT, name) + end if b.name_to_con === nothing b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) end @@ -687,6 +854,11 @@ end function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{MOI.ConstraintIndex}, name::String) + if !Constraint.has_bridges(Constraint.bridges(b)) && + !Variable.has_bridges(Variable.bridges(b)) + # `name_to_con` is not defined for `Objective.SingleBridgeOptimizer`. + return MOI.get(b.model, IdxT, name) + end if b.name_to_con === nothing b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) end @@ -837,16 +1009,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 ee690759b4..70d2924cf1 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 # List of types of available bridges variable_bridge_types::Vector{Any} @@ -33,16 +35,25 @@ mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer constraint_dist::Dict{Tuple{DataType, DataType}, Int} # (F, S) -> Bridge to be used for an `F`-in-`S` constraint constraint_best::Dict{Tuple{DataType, DataType}, DataType} + # List of types of available bridges + objective_bridge_types::Vector{Any} + # (F, S) -> Number of bridges that need to be used for an `F`-in-`S` constraint + objective_dist::Dict{Tuple{DataType}, Int} + # (F, S) -> Bridge to be used for an `F`-in-`S` constraint + objective_best::Dict{Tuple{DataType}, DataType} end function LazyBridgeOptimizer(model::MOI.ModelLike) return LazyBridgeOptimizer{typeof(model)}( model, Variable.Map(), Dict{MOI.VariableIndex, String}(), nothing, Constraint.Map(), Dict{MOI.ConstraintIndex, String}(), nothing, + Objective.Map(), Any[], Dict{Tuple{DataType}, Int}(), Dict{Tuple{DataType}, DataType}(), Any[], Dict{Tuple{DataType, DataType}, Int}(), - Dict{Tuple{DataType, DataType}, DataType}()) + Dict{Tuple{DataType, DataType}, DataType}(), + Any[], Dict{Tuple{DataType}, Int}(), + Dict{Tuple{DataType}, DataType}()) end function Variable.bridges(bridge::LazyBridgeOptimizer) @@ -51,6 +62,9 @@ end function Constraint.bridges(bridge::LazyBridgeOptimizer) return bridge.constraint_map end +function Objective.bridges(b::LazyBridgeOptimizer) + return b.objective_map +end variable_function_type(::Type{<:MOI.AbstractScalarSet}) = MOI.SingleVariable variable_function_type(::Type{<:MOI.AbstractVectorSet}) = MOI.VectorOfVariables @@ -70,22 +84,61 @@ function _dist(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{ end end -function added_dist(b::LazyBridgeOptimizer, args...) +function _dist(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction}) + if MOI.supports(b.model, MOI.ObjectiveFunction{F}()) + return 0 + else + return get(b.objective_dist, (F,), typemax(Int)) + end +end + +function _added_dist(b::LazyBridgeOptimizer, args...) dist = mapreduce(C -> _dist(b, C[1]), +, added_constrained_variable_types(args...), init = 0) dist += mapreduce(C -> _dist(b, C[1], C[2]), +, added_constraint_types(args...), init = 0) return dist end -function supports_added_no_update(b::LazyBridgeOptimizer, args...) +function added_dist(b::LazyBridgeOptimizer, + BT::Type{<:Union{Variable.AbstractBridge, + Constraint.AbstractBridge}}, + args...) + return _added_dist(b, BT, args...) +end +function added_dist(b::LazyBridgeOptimizer, + BT::Type{<:Objective.AbstractBridge}, + args...) + F = set_objective_function_type(BT, args...) + return _added_dist(b, BT, args...) + _dist(b, F) +end + +function _supports_added_no_update(b::LazyBridgeOptimizer, args...) return all(C -> supports_no_update(b, C[1]), added_constrained_variable_types(args...)) && all(C -> supports_no_update(b, C[1], C[2]), added_constraint_types(args...)) end +function supports_added_no_update( + b::LazyBridgeOptimizer, + BT::Type{<:Union{Variable.AbstractBridge, + Constraint.AbstractBridge}}, + args... +) + return _supports_added_no_update(b, BT, args...) +end +function supports_added_no_update( + b::LazyBridgeOptimizer, + BT::Type{<:Objective.AbstractBridge}, + args... +) + F = set_objective_function_type(BT, args...) + return _supports_added_no_update(b, BT, args...) && + supports_no_update(b, F) +end + # Update `b.variable_dist`, `b.constraint_dist` `b.variable_best` and # `b.constraint_best` for constrained variable types in `variables` and # constraint types in `constraints`. -function update_dist!(b::LazyBridgeOptimizer, variables, constraints) +function update_dist!(b::LazyBridgeOptimizer, variables, constraints, objectives) # Bellman-Ford algorithm changed = true # Has b.constraint_dist changed in the last iteration ? while changed @@ -120,11 +173,27 @@ function update_dist!(b::LazyBridgeOptimizer, variables, constraints) end end end + for BT in b.objective_bridge_types + for (F,) in objectives + if Objective.supports_objective_function(BT, F) && + supports_added_no_update(b, BT, F) + # Number of bridges needed using BT + dist = 1 + added_dist(b, BT, F) + # Is it better that what can currently be done ? + if dist < _dist(b, F) + b.objective_dist[(F,)] = dist + b.objective_best[(F,)] = Objective.concrete_bridge_type(BT, F) + changed = true + end + end + end + end end end function fill_required!(required_variables::Set{Tuple{DataType}}, required_constraints::Set{Tuple{DataType, DataType}}, + required_objectives::Set{Tuple{DataType}}, b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) if supports_no_update(b, S) return # The constraint is supported @@ -138,10 +207,10 @@ function fill_required!(required_variables::Set{Tuple{DataType}}, for BT in b.variable_bridge_types if Variable.supports_constrained_variable(BT, S) for C in added_constrained_variable_types(BT, S) - fill_required!(required_variables, required_constraints, b, C[1]) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1]) end for C in added_constraint_types(BT, S) - fill_required!(required_variables, required_constraints, b, C[1], C[2]) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1], C[2]) end end end @@ -149,6 +218,7 @@ end function fill_required!(required_variables::Set{Tuple{DataType}}, required_constraints::Set{Tuple{DataType, DataType}}, + required_objectives::Set{Tuple{DataType}}, b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) if supports_no_update(b, F, S) @@ -165,21 +235,68 @@ function fill_required!(required_variables::Set{Tuple{DataType}}, for BT in b.constraint_bridge_types if MOI.supports_constraint(BT, F, S) for C in added_constrained_variable_types(BT, F, S) - fill_required!(required_variables, required_constraints, b, C[1]) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1]) end for C in added_constraint_types(BT, F, S) - fill_required!(required_variables, required_constraints, b, C[1], C[2]) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1], C[2]) end end end end -# Compute dist[(F, S)], dist[(S,)] and best[(F, S)], best[(S,)] -function update!(b::LazyBridgeOptimizer, types::Tuple) +function fill_required!(required_variables::Set{Tuple{DataType}}, + required_constraints::Set{Tuple{DataType, DataType}}, + required_objectives::Set{Tuple{DataType}}, + b::LazyBridgeOptimizer, + F::Type{<:MOI.AbstractScalarFunction}) + if supports_no_update(b, F) + return # The objective is supported + end + if (F,) in required_objectives + return # The requirements for this objective have already been added or are being added + end + # The objective is not supported yet, add + # * in `required_variables` the required constrained variables types, + # * in `required_constraints` the required constraint types and + # * in `required_objectives` the required objective types + # to bridge it. + push!(required_objectives, (F,)) + for BT in b.objective_bridge_types + if Objective.supports_objective_function(BT, F) + for C in added_constrained_variable_types(BT, F) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1]) + end + for C in added_constraint_types(BT, F) + fill_required!(required_variables, required_constraints, required_objectives, b, C[1], C[2]) + end + fill_required!(required_variables, required_constraints, required_objectives, b, + set_objective_function_type(BT, F)) + end + end +end + +function required(b::LazyBridgeOptimizer, types::Tuple) required_variables = Set{Tuple{DataType}}() required_constraints = Set{Tuple{DataType, DataType}}() - fill_required!(required_variables, required_constraints, b, types...) - update_dist!(b, required_variables, required_constraints) + required_objectives = Set{Tuple{DataType}}() + fill_required!(required_variables, required_constraints, + required_objectives, b, types...) + return required_variables, required_constraints, required_objectives +end + +# Compute dist[(F, S)], dist[(S,)] and best[(F, S)], best[(S,)] +function update!(b::LazyBridgeOptimizer, types::Tuple) + update_dist!(b, required(b, types)...) +end + +# After `add_bridge(b, BT)`, some constrained variables `(S,)` in +# `keys(b.variable_best)` or constraints `(F, S)` in `keys(b.constraint_best)` +# or `(F,)` in `keys(b.objective_best)` may be bridged +# with less bridges than `b.variable_dist[(S,)]`, +# `b.constraint_dist[(F, S)]` or `b.objective_dist[(F,)]` using `BT`. +function _update_key_dists!(b) + # TODO we should call `fill_required!`. + update_dist!(b, keys(b.variable_best), keys(b.constraint_best), keys(b.objective_best)) end """ @@ -189,11 +306,7 @@ Enable the use of the variable bridges of type `BT` by `b`. """ function add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.AbstractBridge}) push!(b.variable_bridge_types, BT) - # Some constrained variables `(S,)` in `keys(b.variable_best)` or - # constraints `(F, S)` in `keys(b.constraint_best)` may now be bridged - # with a less bridges than `b.variable_dist[(F, S)]` or - # `b.constraint_dist[(F, S)]` using `BT`. - update_dist!(b, keys(b.variable_best), keys(b.constraint_best)) + update_dist!(b, keys(b.variable_best), keys(b.constraint_best), keys(b.objective_best)) end """ @@ -203,11 +316,17 @@ Enable the use of the constraint bridges of type `BT` by `b`. """ function add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Constraint.AbstractBridge}) push!(b.constraint_bridge_types, BT) - # Some constrained variables `(S,)` in `keys(b.variable_best)` or - # constraints `(F, S)` in `keys(b.constraint_best)` may now be bridged - # with a less bridges than `b.variable_dist[(F, S)]` or - # `b.constraint_dist[(F, S)]` using `BT`. - update_dist!(b, keys(b.variable_best), keys(b.constraint_best)) + update_dist!(b, keys(b.variable_best), keys(b.constraint_best), keys(b.objective_best)) +end + +""" + add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Objective.AbstractBridge}) + +Enable the use of the objective bridges of type `BT` by `b`. +""" +function add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Objective.AbstractBridge}) + push!(b.objective_bridge_types, BT) + update_dist!(b, keys(b.variable_best), keys(b.constraint_best), keys(b.objective_best)) end function _remove_bridge(bridge_types::Vector, BT::Type) @@ -255,6 +374,9 @@ end function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) return !MOI.supports_constraint(b.model, F, S) end +function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction}) + return !MOI.supports(b.model, MOI.ObjectiveFunction{F}()) +end # Same as supports_constraint but do not trigger `update!`. This is # used inside `update!`. function supports_no_update(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) @@ -263,6 +385,9 @@ end function supports_no_update(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) return MOI.supports_constraint(b.model, F, S) || (F, S) in keys(b.constraint_best) end +function supports_no_update(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction}) + return MOI.supports(b.model, MOI.ObjectiveFunction{F}()) || (F,) in keys(b.objective_best) +end supports_constraint_bridges(::LazyBridgeOptimizer) = true function supports_bridging_constrained_variable( @@ -278,7 +403,13 @@ function supports_bridging_constraint( update!(b, (F, S)) return (F, S) in keys(b.constraint_best) end -function bridge_type(b::LazyBridgeOptimizer{BT}, S::Type{<:MOI.AbstractSet}) where BT +function supports_bridging_objective_function( + b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction} +) + update!(b, (F,)) + return (F,) in keys(b.objective_best) +end +function bridge_type(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) update!(b, (S,)) result = get(b.variable_best, (S,), nothing) if result === nothing @@ -286,7 +417,7 @@ function bridge_type(b::LazyBridgeOptimizer{BT}, S::Type{<:MOI.AbstractSet}) whe end return result end -function bridge_type(b::LazyBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) where BT +function bridge_type(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) update!(b, (F, S)) result = get(b.constraint_best, (F, S), nothing) if result === nothing @@ -294,3 +425,11 @@ function bridge_type(b::LazyBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction} end return result end +function bridge_type(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction}) + update!(b, (F,)) + result = get(b.objective_best, (F,), nothing) + if result === nothing + throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction{F}())) + end + return result +end diff --git a/test/Bridges/Bridges.jl b/test/Bridges/Bridges.jl index cffdd7ba8a..532c93c091 100644 --- a/test/Bridges/Bridges.jl +++ b/test/Bridges/Bridges.jl @@ -23,3 +23,9 @@ end _timed_include(joinpath(constraint_dir, file)) end end +@testset "Objective bridges" begin + objective_dir = joinpath(@__DIR__, "Objective") + @testset "$(file)" for file in readdir(objective_dir) + _timed_include(joinpath(objective_dir, file)) + end +end diff --git a/test/Bridges/Objective/functionize.jl b/test/Bridges/Objective/functionize.jl new file mode 100644 index 0000000000..f6a675f209 --- /dev/null +++ b/test/Bridges/Objective/functionize.jl @@ -0,0 +1,34 @@ +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.Model{Float64}()) +config = MOIT.TestConfig() + +bridged_mock = MOIB.Objective.Functionize{Float64}(mock) + +@testset "solve_singlevariable_obj" begin + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0], MOI.FEASIBLE_POINT) + ) + MOIT.solve_singlevariable_obj(bridged_mock, config) + @test MOI.get(mock, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64} + @test MOI.get(bridged_mock, MOI.ObjectiveFunctionType()) == MOI.SingleVariable + @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MIN_SENSE + @test MOI.get(bridged_mock, MOI.ObjectiveSense()) == MOI.MIN_SENSE + vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) + func = MOI.SingleVariable(vis[1]) + @test MOI.get(mock, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) ≈ + convert(MOI.ScalarAffineFunction{Float64}, func) + @test MOI.get(bridged_mock, MOI.ObjectiveFunction{MOI.SingleVariable}()) == func + MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MAX_SENSE + @test MOI.get(bridged_mock, MOI.ObjectiveSense()) == MOI.MAX_SENSE + test_delete_objective(bridged_mock, 1, tuple()) +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 diff --git a/test/Bridges/Objective/slack.jl b/test/Bridges/Objective/slack.jl new file mode 100644 index 0000000000..05601cb63f --- /dev/null +++ b/test/Bridges/Objective/slack.jl @@ -0,0 +1,45 @@ +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.Model{Float64}()) +config = MOIT.TestConfig() + +bridged_mock = MOIB.Objective.Slack{Float64}(mock) + +@testset "Set objective before sense" begin + err = ErrorException( + "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when" * + " using `MOI.Bridges.Objective.SlackBridge`." + ) + F = MOI.ScalarAffineFunction{Float64} + @test_throws err MOI.set(bridged_mock, MOI.ObjectiveFunction{F}(), zero(F)) +end + +@testset "solve_qp_edge_cases" begin + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]) + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]) + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]) + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]) + ) + ) + MOIT.solve_qp_edge_cases(mock, config) +end diff --git a/test/Bridges/Variable/zeros.jl b/test/Bridges/Variable/zeros.jl index cd4b381613..0cf7fbbf85 100644 --- a/test/Bridges/Variable/zeros.jl +++ b/test/Bridges/Variable/zeros.jl @@ -25,8 +25,6 @@ fy = MOI.SingleVariable(y) fz = MOI.SingleVariable(z) @testset "SingleVariable objective" begin - err = ErrorException("Using bridged variable in `SingleVariable` function.") - @test_throws err MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fy)}(), fy) MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}(), fx) @test MOI.get(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}()) == fx diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index a92e568bf5..c58546ea4f 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -152,6 +152,19 @@ end MOI.LessThan{T}) == MOIB.Constraint.QuadtoSOCBridge{T} end end + @testset "Objective" begin + F = MOI.ScalarQuadraticFunction{T} + @test !MOI.supports(bridged, MOI.ObjectiveFunction{MOI.SingleVariable}()) + @test !MOI.supports(bridged, MOI.ObjectiveFunction{F}()) + MOIB.add_bridge(bridged, MOIB.Objective.SlackBridge{T}) + @test !MOI.supports(bridged, MOI.ObjectiveFunction{MOI.SingleVariable}()) + @test !MOI.supports(bridged, MOI.ObjectiveFunction{F}()) + MOIB.add_bridge(bridged, MOIB.Objective.FunctionizeBridge{T}) + @test MOI.supports(bridged, MOI.ObjectiveFunction{MOI.SingleVariable}()) + @test MOIB.bridge_type(bridged, MOI.SingleVariable) == MOIB.Objective.FunctionizeBridge{T} + @test MOI.supports(bridged, MOI.ObjectiveFunction{F}()) + @test MOIB.bridge_type(bridged, F) == MOIB.Objective.SlackBridge{T, F, F} + end end @testset "Continuous Linear" begin diff --git a/test/Bridges/utilities.jl b/test/Bridges/utilities.jl index 15cc61292e..89e7c91234 100644 --- a/test/Bridges/utilities.jl +++ b/test/Bridges/utilities.jl @@ -93,3 +93,24 @@ function test_delete_bridged_variables( test_num_constraints(m, num_constraints...) end end +function test_delete_objective( + m::MOIB.AbstractBridgeOptimizer, nvars::Int, + nocs::Tuple; used_bridges = 1) + function num_bridges() + return length(MOIB.Objective.bridges(m)) + end + start_num_bridges = num_bridges() + @test MOI.get(m, MOI.NumberOfVariables()) == nvars + @test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars + for noc in nocs + test_noc(m, noc...) + end + MOI.set(m, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + @test MOI.get(m, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE + @test num_bridges() == start_num_bridges - used_bridges + @test MOI.get(m, MOI.NumberOfVariables()) == nvars + @test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars + for noc in nocs + test_noc(m, noc...) + end +end