From 703a3ce000407b2ef14b69762bbc2f36b560586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 29 Aug 2019 16:48:50 +0200 Subject: [PATCH 1/5] Add Objective bridges --- src/Bridges/Bridges.jl | 1 + .../Constraint/single_bridge_optimizer.jl | 3 + src/Bridges/Objective/Objective.jl | 21 ++ src/Bridges/Objective/functionize.jl | 43 ++++ .../Objective/single_bridge_optimizer.jl | 42 ++++ src/Bridges/Objective/slack.jl | 76 ++++++ .../Variable/single_bridge_optimizer.jl | 3 + src/Bridges/bridge_optimizer.jl | 186 +++++++++++++- src/Bridges/lazy_bridge_optimizer.jl | 230 +++++++++++++++--- test/Bridges/Objective/functionize.jl | 34 +++ test/Bridges/Objective/slack.jl | 45 ++++ test/Bridges/Variable/zeros.jl | 2 - test/Bridges/lazy_bridge_optimizer.jl | 13 + test/Bridges/utilities.jl | 21 ++ 14 files changed, 667 insertions(+), 53 deletions(-) create mode 100644 src/Bridges/Objective/functionize.jl create mode 100644 src/Bridges/Objective/single_bridge_optimizer.jl create mode 100644 src/Bridges/Objective/slack.jl create mode 100644 test/Bridges/Objective/functionize.jl create mode 100644 test/Bridges/Objective/slack.jl diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index ed08b31e91..a50989643d 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -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 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 index 4727fb0aac..ad61866b68 100644 --- a/src/Bridges/Objective/Objective.jl +++ b/src/Bridges/Objective/Objective.jl @@ -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 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/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_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 2e7262dbda..409de0930f 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) @@ -314,7 +359,7 @@ function reduce_bridged( else value = model_value() end - variable_function = F == variable_function_type(S) + variable_function = F == MOIU.variable_function_type(S) if variable_function && is_bridged(b, S) value = operate_variable_bridges!(value) end @@ -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 @@ -701,7 +873,7 @@ function MOI.supports_constraint(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) if is_bridged(b, F, S) - if F == variable_function_type(S) && + if F == MOIU.variable_function_type(S) && supports_bridging_constrained_variable(b, S) return true 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..de23787f99 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,14 +62,17 @@ 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 function _dist(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) - if MOI.supports_constraint(b.model, variable_function_type(S), S) + F = MOIU.variable_function_type(S) + if MOI.supports_constraint(b.model, F, S) return 0 else - return get(b.variable_dist, (S,), typemax(Int)) + return min(get(b.variable_dist, (S,), typemax(Int)), + get(b.constraint_dist, (F, S), typemax(Int) - 1) + 1) end end @@ -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,49 @@ 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, + BT::Type{<:AbstractBridge}) + for C in added_constrained_variable_types(BT) + fill_required!(required_variables, required_constraints, + required_objectives, b, C[1]) + F = MOIU.variable_function_type(C[1]) + fill_required!(required_variables, required_constraints, + required_objectives, b, F, C[1]) + end + for C in added_constraint_types(BT) + fill_required!(required_variables, required_constraints, + required_objectives, b, C[1], C[2]) + end + if BT <: Objective.AbstractBridge + fill_required!(required_variables, required_constraints, required_objectives, b, + set_objective_function_type(BT)) + 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 @@ -137,18 +228,16 @@ function fill_required!(required_variables::Set{Tuple{DataType}}, push!(required_variables, (S,)) 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]) - end - for C in added_constraint_types(BT, S) - fill_required!(required_variables, required_constraints, b, C[1], C[2]) - end + fill_required!(required_variables, required_constraints, + required_objectives, b, + Variable.concrete_bridge_type(BT, S)) end end 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) @@ -164,22 +253,61 @@ function fill_required!(required_variables::Set{Tuple{DataType}}, push!(required_constraints, (F, S)) 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]) - end - for C in added_constraint_types(BT, F, S) - fill_required!(required_variables, required_constraints, b, C[1], C[2]) - end + fill_required!(required_variables, required_constraints, + required_objectives, b, + Constraint.concrete_bridge_type(BT, F, S)) 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) + fill_required!(required_variables, required_constraints, + required_objectives, b, + Objective.concrete_bridge_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 +317,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 +327,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) @@ -250,19 +380,27 @@ end # It only bridges when the constraint is not supporting, hence the name "Lazy" function is_bridged(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) - return !MOI.supports_constraint(b.model, variable_function_type(S), S) + return !MOI.supports_constraint(b.model, MOIU.variable_function_type(S), S) 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}) - return MOI.supports_constraint(b.model, variable_function_type(S), S) || (S,) in keys(b.variable_best) + F = MOIU.variable_function_type(S) + return MOI.supports_constraint(b.model, MOIU.variable_function_type(S), S) || + (S,) in keys(b.variable_best) || (F, S) in keys(b.constraint_best) 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,15 +416,21 @@ 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 - throw(MOI.UnsupportedConstraint{variable_function_type(S), S}()) + throw(MOI.UnsupportedConstraint{MOIU.variable_function_type(S), S}()) 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 +438,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/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/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 3e9d5339fe..35d1111eef 100644 --- a/test/Bridges/utilities.jl +++ b/test/Bridges/utilities.jl @@ -110,3 +110,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 From 1059659965160e0ac118156edc185206f812576c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 4 Sep 2019 16:57:43 +0200 Subject: [PATCH 2/5] Address @odow comments --- src/Bridges/Objective/slack.jl | 2 +- src/Bridges/bridge_optimizer.jl | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Bridges/Objective/slack.jl b/src/Bridges/Objective/slack.jl index b4ec0f9fce..a1cc85da46 100644 --- a/src/Bridges/Objective/slack.jl +++ b/src/Bridges/Objective/slack.jl @@ -8,7 +8,7 @@ The `SlackBridge` converts an objective function of type `G` into a `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. +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 diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 409de0930f..d4f309fba8 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -518,6 +518,15 @@ function MOI.supports(b::AbstractBridgeOptimizer, return MOI.supports(b.model, attr) end end + +""" + struct ObjectiveFunctionValue{F<:MOI.AbstractScalarFunction} end + +Attribute for the value of the objective function of type `F`. If the objective +of the objective function does not depend on `F`, the type `F` determines +whether the computation is redirected to an objective bridge or to the +underlying model. +""" struct ObjectiveFunctionValue{F<:MOI.AbstractScalarFunction} end function MOI.get(b::AbstractBridgeOptimizer, attr::ObjectiveFunctionValue{F}) where F From f96a3dcfcf6219e2dee4cffd48697e52df24431a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 4 Sep 2019 20:54:06 +0200 Subject: [PATCH 3/5] Add comments --- src/Bridges/Objective/functionize.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Bridges/Objective/functionize.jl b/src/Bridges/Objective/functionize.jl index f5381ba63e..c5ce83021b 100644 --- a/src/Bridges/Objective/functionize.jl +++ b/src/Bridges/Objective/functionize.jl @@ -24,10 +24,14 @@ function MOIB.set_objective_function_type(::Type{FunctionizeBridge{T}}) where T return MOI.ScalarAffineFunction{T} 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}, From ccba971a998945ffac4f9f3381a97ca134fad8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 4 Sep 2019 23:58:13 +0200 Subject: [PATCH 4/5] Add tests --- src/Bridges/Objective/functionize.jl | 8 ++++++++ src/Bridges/Objective/slack.jl | 21 ++++++++++++++++++++- src/Bridges/bridge_optimizer.jl | 20 +++++++++++++++++++- src/Utilities/model.jl | 9 +++++++-- test/Bridges/Objective/slack.jl | 14 +++++++++----- test/Bridges/utilities.jl | 11 ++++++----- 6 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Bridges/Objective/functionize.jl b/src/Bridges/Objective/functionize.jl index c5ce83021b..3971254710 100644 --- a/src/Bridges/Objective/functionize.jl +++ b/src/Bridges/Objective/functionize.jl @@ -24,6 +24,14 @@ 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 diff --git a/src/Bridges/Objective/slack.jl b/src/Bridges/Objective/slack.jl index a1cc85da46..fd75151ea1 100644 --- a/src/Bridges/Objective/slack.jl +++ b/src/Bridges/Objective/slack.jl @@ -28,7 +28,7 @@ function bridge_objective(::Type{SlackBridge{T, F, G}}, model::MOI.ModelLike, error("Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when", " using `MOI.Bridges.Objective.SlackBridge`.") end - constraint = MOIU.add_scalar_constraint(model, f, set) + 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 @@ -54,6 +54,25 @@ function concrete_bridge_type(::Type{<:SlackBridge{T}}, 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) diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index d4f309fba8..ea0706ccfd 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -412,6 +412,9 @@ function MOI.get(b::AbstractBridgeOptimizer, for bridge in values(Constraint.bridges(b)) _remove_bridged(list, bridge, attr) end + for bridge in values(Objective.bridges(b)) + _remove_bridged(list, bridge, attr) + end return list end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfVariables) @@ -422,6 +425,9 @@ function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfVariables) for bridge in values(Constraint.bridges(b)) s -= MOI.get(bridge, attr) end + for bridge in values(Objective.bridges(b)) + s -= MOI.get(bridge, attr) + end return s end @@ -446,6 +452,9 @@ function MOI.get(b::AbstractBridgeOptimizer, for bridge in values(Constraint.bridges(b)) s -= MOI.get(bridge, attr) end + for bridge in values(Objective.bridges(b)) + s -= MOI.get(bridge, attr) + end return s end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraints) @@ -569,6 +578,7 @@ function MOI.get(b::AbstractBridgeOptimizer, end function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveSense, value::MOI.OptimizationSense) + MOI.set(b.model, attr, value) if is_objective_bridged(b) if value == MOI.FEASIBILITY_SENSE _delete_objective_bridges(b) @@ -578,7 +588,6 @@ function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveSense, end end end - MOI.set(b.model, attr, value) end function _bridge_objective(b, BridgeType, func) bridge = Objective.bridge_objective(BridgeType, b, func) @@ -588,7 +597,16 @@ function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveFunction, func::MOI.AbstractScalarFunction) if is_objective_bridged(b) + # Clear objective function by setting sense to `MOI.FEASIBILITY_SENSE` + # first. This is needed if the objective function of `b.model` is + # `MOI.SingleVariable(slack)` where `slack` is the slack variable + # created by `Objective.SlackBridge`. + sense = MOI.get(b.model, MOI.ObjectiveSense()) + MOI.set(b.model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) _delete_objective_bridges(b) + if sense != MOI.FEASIBILITY_SENSE + MOI.set(b.model, MOI.ObjectiveSense(), sense) + end end if Variable.has_bridges(Variable.bridges(b)) if func isa MOI.SingleVariable diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 77cc4747ad..53e46abd7d 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -357,7 +357,12 @@ end # Objective MOI.get(model::AbstractModel, ::MOI.ObjectiveSense) = model.sense MOI.supports(model::AbstractModel, ::MOI.ObjectiveSense) = true -function MOI.set(model::AbstractModel, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) +function MOI.set(model::AbstractModel{T}, ::MOI.ObjectiveSense, + sense::MOI.OptimizationSense) where T + if sense == MOI.FEASIBILITY_SENSE + model.objectiveset = false + model.objective = zero(MOI.ScalarAffineFunction{T}) + end model.senseset = true model.sense = sense end @@ -922,7 +927,7 @@ macro model(model_name, ss, sst, vs, vst, sf, sft, vf, vft) model.senseset = false model.sense = $MOI.FEASIBILITY_SENSE model.objectiveset = false - model.objective = $SAF{T}(MOI.ScalarAffineTerm{T}[], zero(T)) + model.objective = zero($MOI.ScalarAffineFunction{T}) model.num_variables_created = 0 model.variable_indices = nothing model.single_variable_mask = UInt8[] diff --git a/test/Bridges/Objective/slack.jl b/test/Bridges/Objective/slack.jl index 05601cb63f..c76553ccb4 100644 --- a/test/Bridges/Objective/slack.jl +++ b/test/Bridges/Objective/slack.jl @@ -26,20 +26,24 @@ end MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]) + (MOI.FEASIBLE_POINT, [1.0, 2.0, 5.0]) ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]) + (MOI.FEASIBLE_POINT, [1.0, 2.0, 7.0]) ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]) + (MOI.FEASIBLE_POINT, [1.0, 2.0, 2.0]) ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]) + (MOI.FEASIBLE_POINT, [1.0, 2.0, 7.0]) ) ) - MOIT.solve_qp_edge_cases(mock, config) + MOIT.solve_qp_edge_cases(bridged_mock, config) + test_delete_objective(bridged_mock, 2, ( + (MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}, 0), + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, 0), + )) end diff --git a/test/Bridges/utilities.jl b/test/Bridges/utilities.jl index 35d1111eef..fd21c9ec75 100644 --- a/test/Bridges/utilities.jl +++ b/test/Bridges/utilities.jl @@ -112,22 +112,23 @@ function test_delete_bridged_variables( end function test_delete_objective( m::MOIB.AbstractBridgeOptimizer, nvars::Int, - nocs::Tuple; used_bridges = 1) + list_num_constraints::Tuple; used_bridges = 1) + warn_incomplete_list_num_constraints(typeof(MOIB.Objective.root_bridge(MOIB.Objective.bridges(m))), list_num_constraints) 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...) + for num_constraints in list_num_constraints + test_num_constraints(m, num_constraints...) 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...) + for num_constraints in list_num_constraints + test_num_constraints(m, num_constraints...) end end From 98f50635446a752abe3b83e8b932a280d1a08597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 5 Sep 2019 11:27:33 +0200 Subject: [PATCH 5/5] Add tests --- .../Objective/single_bridge_optimizer.jl | 2 +- src/Bridges/Objective/slack.jl | 8 +- src/Bridges/bridge_optimizer.jl | 21 ++++- src/Bridges/lazy_bridge_optimizer.jl | 12 +++ test/Bridges/Objective/slack.jl | 93 +++++++++++++++++++ test/Bridges/lazy_bridge_optimizer.jl | 38 ++++++++ 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/src/Bridges/Objective/single_bridge_optimizer.jl b/src/Bridges/Objective/single_bridge_optimizer.jl index 67c40b8594..5d87d1fd44 100644 --- a/src/Bridges/Objective/single_bridge_optimizer.jl +++ b/src/Bridges/Objective/single_bridge_optimizer.jl @@ -29,7 +29,7 @@ function MOIB.is_bridged(::SingleBridgeOptimizer, return false end function MOIB.supports_bridging_objective_function( - ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractScalarFunction}) where BT + ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractScalarFunction}) where BT return supports_objective_function(BT, F) end function MOIB.is_bridged( diff --git a/src/Bridges/Objective/slack.jl b/src/Bridges/Objective/slack.jl index fd75151ea1..8b6a1adcf0 100644 --- a/src/Bridges/Objective/slack.jl +++ b/src/Bridges/Objective/slack.jl @@ -88,8 +88,8 @@ function MOI.get(model::MOI.ModelLike, 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) +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 diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index ea0706ccfd..fb59c52ebb 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -492,6 +492,18 @@ function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraints) end # Model an optimizer attributes +function MOI.get(b::AbstractBridgeOptimizer, + attr::MOI.ListOfModelAttributesSet) + list = MOI.get(b.model, attr) + if is_objective_bridged(b) + list = copy(list) + # There should be a `MOI.ObjectiveFunction` in `list` otherwise + # `is_objective_bridged` would return `false`. + deleteat!(list, findfirst(attr -> attr isa MOI.ObjectiveFunction, list)) + push!(list, MOI.ObjectiveFunction{MOI.get(b, MOI.ObjectiveFunctionType())}()) + end + return unbridged_function(b, list) +end function MOI.get(b::AbstractBridgeOptimizer, attr::Union{MOI.AbstractModelAttribute, MOI.AbstractOptimizerAttribute}) @@ -589,6 +601,11 @@ function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ObjectiveSense, end end end +function objective_functionize_bridge(b::AbstractBridgeOptimizer) + error("Need to apply a `MOI.Bridges.Objective.FunctionizeBridge` to a", + " `SingleVariable` objective function because the variable is", + " bridged but objective bridges are not supported by `$(typeof(b))`.") +end function _bridge_objective(b, BridgeType, func) bridge = Objective.bridge_objective(BridgeType, b, func) Objective.add_key_for_bridge(Objective.bridges(b), bridge, func) @@ -612,7 +629,7 @@ function MOI.set(b::AbstractBridgeOptimizer, if func isa MOI.SingleVariable if is_bridged(b, func.variable) BridgeType = Objective.concrete_bridge_type( - Objective.FunctionizeBridge{Float64}, typeof(func)) + objective_functionize_bridge(b), typeof(func)) _bridge_objective(b, BridgeType, func) return end @@ -1059,7 +1076,7 @@ function MOI.add_constrained_variables(b::AbstractBridgeOptimizer, set::MOI.AbstractVectorSet) if is_bridged(b, typeof(set)) || is_bridged(b, MOI.VectorOfVariables, typeof(set)) - if supports_bridging_constrained_variable(b, typeof(set)) + if set isa MOI.Reals || supports_bridging_constrained_variable(b, typeof(set)) BridgeType = Variable.concrete_bridge_type(b, typeof(set)) bridge = Variable.bridge_constrained_variable(BridgeType, b, set) return Variable.add_keys_for_bridge(Variable.bridges(b), bridge, set) diff --git a/src/Bridges/lazy_bridge_optimizer.jl b/src/Bridges/lazy_bridge_optimizer.jl index de23787f99..64e000a824 100644 --- a/src/Bridges/lazy_bridge_optimizer.jl +++ b/src/Bridges/lazy_bridge_optimizer.jl @@ -446,3 +446,15 @@ function bridge_type(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunctio end return result end + +function objective_functionize_bridge(b::LazyBridgeOptimizer) + index = findfirst(bridge_type -> bridge_type <: Objective.FunctionizeBridge, + b.objective_bridge_types) + if index === nothing + error("Need to apply a `MOI.Bridges.Objective.FunctionizeBridge` to a", + " `SingleVariable` objective function because the variable is", + " bridged but no such objective bridge type was added. Add one", + " with `add_bridge`.") + end + return b.objective_bridge_types[index] +end diff --git a/test/Bridges/Objective/slack.jl b/test/Bridges/Objective/slack.jl index c76553ccb4..6e8144aeb6 100644 --- a/test/Bridges/Objective/slack.jl +++ b/test/Bridges/Objective/slack.jl @@ -42,6 +42,99 @@ end ) ) MOIT.solve_qp_edge_cases(bridged_mock, config) + + @test MOIB.is_objective_bridged(bridged_mock) + @test MOI.get(bridged_mock, MOI.ObjectiveFunctionType()) == MOI.ScalarQuadraticFunction{Float64} + @test MOI.get(bridged_mock, MOI.ListOfModelAttributesSet()) == [ + MOI.ObjectiveSense(), + MOI.ObjectiveFunction{ MOI.ScalarQuadraticFunction{Float64}}() + ] + + con_names = ["cx", "cy"] + cxcy = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) + MOI.set(bridged_mock, MOI.ConstraintName(), cxcy, con_names) + + var_names = ["x", "y"] + xy = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) + MOI.set(bridged_mock, MOI.VariableName(), xy, var_names) + @testset "Test mock model" begin + abs = MOI.get(mock, MOI.ListOfVariableIndices()) + @test length(abs) == 3 + MOI.set(mock, MOI.VariableName(), abs[3], "s") + cquad = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) + MOI.set(bridged_mock, MOI.ConstraintName(), cquad[1], "quad") + + s = """ + variables: x, y, s + cx: x >= 1.0 + cy: y >= 2.0 + quad: 1.0 * x * x + 1.0 * x * y + 1.0 * y * y + -1.0 * s <= 0.0 + minobjective: s + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(mock, model, [var_names; "s"], [con_names; "quad"]) + end + + bridged_var_names = ["x", "y"] + @testset "Test bridged model" begin + s = """ + variables: x, y + cx: x >= 1.0 + cy: y >= 2.0 + minobjective: 1.0 * x * x + 1.0 * x * y + 1.0 * y * y + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(bridged_mock, model, var_names, con_names) + end + + err = ArgumentError( + "Objective bridge of type `MathOptInterface.Bridges.Objective.SlackBridge{Float64,MathOptInterface.ScalarQuadraticFunction{Float64},MathOptInterface.ScalarQuadraticFunction{Float64}}`" * + " does not support modifying the objective sense. As a workaround, set" * + " the sense to `MOI.FEASIBILITY_SENSE` to clear the objective function" * + " and bridges." + ) + @test_throws err MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MAX_SENSE) + obj = MOI.get(bridged_mock, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}()) + MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + @test !MOIB.is_objective_bridged(bridged_mock) + @test MOI.get(bridged_mock, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE + @test MOI.get(bridged_mock, MOI.ListOfModelAttributesSet()) == [MOI.ObjectiveSense()] + MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(bridged_mock, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj) + + @testset "Test mock model" begin + abs = MOI.get(mock, MOI.ListOfVariableIndices()) + @test length(abs) == 3 + MOI.set(mock, MOI.VariableName(), abs[3], "s") + cquad = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}}()) + MOI.set(bridged_mock, MOI.ConstraintName(), cquad[1], "quad") + + s = """ + variables: x, y, s + cx: x >= 1.0 + cy: y >= 2.0 + quad: 1.0 * x * x + 1.0 * x * y + 1.0 * y * y + -1.0 * s >= 0.0 + maxobjective: s + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(mock, model, [var_names; "s"], [con_names; "quad"]) + end + + @testset "Test bridged model" begin + s = """ + variables: x, y + cx: x >= 1.0 + cy: y >= 2.0 + maxobjective: 1.0 * x * x + 1.0 * x * y + 1.0 * y * y + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(bridged_mock, model, var_names, con_names) + end + test_delete_objective(bridged_mock, 2, ( (MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}, 0), (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, 0), diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index c58546ea4f..e2ae1329dd 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -57,6 +57,42 @@ end with inner model SDPAModel{Float64}""" end +@testset "Bridged variable in `SingleVariable` objective function with $T" for T in [Float64, Int] + @testset "No objective bridge" begin + model = SDPAModel{T}() + bridged = MOIB.Variable.Vectorize{T}(model) + x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T))) + fx = MOI.SingleVariable(x) + err = ErrorException("Need to apply a `MOI.Bridges.Objective.FunctionizeBridge` to a" * + " `SingleVariable` objective function because the variable is" * + " bridged but objective bridges are not supported by" * + " `MathOptInterface.Bridges.Variable.SingleBridgeOptimizer{MathOptInterface.Bridges.Variable.VectorizeBridge{$T,S} where S,SDPAModel{$T}}`.") + @test_throws err MOI.set(bridged, MOI.ObjectiveFunction{typeof(fx)}(), fx) + end + @testset "LazyBridgeOptimizer" begin + model = SDPAModel{T}() + bridged = MOIB.LazyBridgeOptimizer(model) + MOIB.add_bridge(bridged, MOIB.Variable.VectorizeBridge{T}) + x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T))) + fx = MOI.SingleVariable(x) + @testset "without `Objective.FunctionizeBridge`" begin + err = ErrorException("Need to apply a `MOI.Bridges.Objective.FunctionizeBridge`" * + " to a `SingleVariable` objective function because the" * + " variable is bridged but no such objective bridge type was" * + " added. Add one with `add_bridge`.") + @test_throws err MOI.set(bridged, MOI.ObjectiveFunction{typeof(fx)}(), fx) + end + @testset "with `Objective.FunctionizeBridge`" begin + MOIB.add_bridge(bridged, MOIB.Objective.FunctionizeBridge{T}) + MOI.set(bridged, MOI.ObjectiveFunction{typeof(fx)}(), fx) + @test MOI.get(bridged, MOI.ObjectiveFunctionType()) == MOI.SingleVariable + a = MOI.get(model, MOI.ListOfVariableIndices())[1] + fa = MOI.SingleVariable(a) + @test MOI.get(model, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{T} + @test MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) ≈ fa + one(T) + end + end +end @testset "SDPA format with $T" for T in [Float64, Int] model = SDPAModel{T}() @@ -79,6 +115,8 @@ end @testset "Free" begin @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Reals) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals) + @test_throws MOI.UnsupportedConstraint{MOI.VectorOfVariables, MOI.Reals} MOI.add_variable(bridged) + @test_throws MOI.UnsupportedConstraint{MOI.VectorOfVariables, MOI.Reals} MOI.add_variables(bridged, 2) MOIB.add_bridge(bridged, MOIB.Variable.FreeBridge{T}) @test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals) @test MOIB.bridge_type(bridged, MOI.Reals) == MOIB.Variable.FreeBridge{T}