diff --git a/src/optimizer_interface.jl b/src/optimizer_interface.jl index 79941426f8f..1068113d7d4 100644 --- a/src/optimizer_interface.jl +++ b/src/optimizer_interface.jl @@ -985,6 +985,114 @@ function optimizer_index(x::Union{VariableRef,ConstraintRef{Model}}) return _moi_optimizer_index(backend(model), index(x)) end +struct ObjectiveFunctionAttribute{A,F} + attr::A +end + +""" + struct ObjectiveDualStart <: MOI.AbstractModelAttribute end + +If the objective function had a dual, it would be `-1` for the Lagrangian +function to be the same. +When the `MOI.Bridges.Objective.SlackBridge` is used, it creates a constraint. +The dual of this constraint is therefore `-1` as well. +When setting this attribute, it allows to set the constraint dual of this +constraint. +""" +struct ObjectiveDualStart <: MOI.AbstractModelAttribute end +# Defining it for `MOI.set` leads to ambiguity +function MOI.throw_set_error_fallback( + ::MOI.ModelLike, + ::ObjectiveDualStart, + value, +) + return nothing +end + +""" + struct ObjectiveSlackGapPrimalStart <: MOI.AbstractModelAttribute end + +If the objective function had a dual, it would be `-1` for the Lagrangian +function to be the same. +When the `MOI.Bridges.Objective.SlackBridge` is used, it creates a constraint. +The dual of this constraint is therefore `-1` as well. +When setting this attribute, it allows to set the constraint dual of this +constraint. +""" +struct ObjectiveSlackGapPrimalStart <: MOI.AbstractModelAttribute end +function MOI.throw_set_error_fallback( + ::MOI.ModelLike, + ::ObjectiveSlackGapPrimalStart, + value, +) + return nothing +end + +function MOI.set( + b::MOI.Bridges.AbstractBridgeOptimizer, + attr::ObjectiveFunctionAttribute{A,F}, + value, +) where {A,F} + obj_attr = MOI.ObjectiveFunction{F}() + if MOI.Bridges.is_bridged(b, obj_attr) + return MOI.set( + MOI.Bridges.recursive_model(b), + attr, + MOI.Bridges.bridge(b, obj_attr), + value, + ) + else + return MOI.set(b.model, attr.attr, value) + end +end + +function MOI.set( + b::MOI.Bridges.AbstractBridgeOptimizer, + attr::Union{ObjectiveDualStart,ObjectiveSlackGapPrimalStart}, + value, +) + if MOI.Bridges.is_objective_bridged(b) + F = MOI.Bridges.Objective.function_type( + MOI.Bridges.Objective.bridges(b), + ) + return MOI.set( + b, + ObjectiveFunctionAttribute{typeof(attr),F}(attr), + value, + ) + else + return MOI.set(b.model, attr, value) + end +end + +function MOI.set( + model::MOI.ModelLike, + ::ObjectiveFunctionAttribute{ObjectiveDualStart}, + b::MOI.Bridges.Objective.SlackBridge, + value, +) + return MOI.set(model, MOI.ConstraintDualStart(), b.constraint, value) +end + +function MOI.set( + model::MOI.ModelLike, + ::ObjectiveFunctionAttribute{ObjectiveSlackGapPrimalStart}, + b::MOI.Bridges.Objective.SlackBridge{T}, + value, +) where {T} + # `f(x) - slack = value` so `slack = f(x) - value` + fun = MOI.get(model, MOI.ConstraintFunction(), b.constraint) + set = MOI.get(model, MOI.ConstraintSet(), b.constraint) + MOI.Utilities.operate!(-, T, fun, MOI.constant(set)) + # `fun = f - slack` so we remove the term `-slack` to get `f` + f = MOI.Utilities.remove_variable(fun, b.slack) + f_val = MOI.Utilities.eval_variables(f) do v + return MOI.get(model, MOI.VariablePrimalStart(), v) + end + MOI.set(model, MOI.VariablePrimalStart(), b.slack, f_val - value) + return MOI.set(model, MOI.ConstraintPrimalStart(), b.constraint, value) +end + """ set_start_values( model::Model; @@ -1090,6 +1198,8 @@ function set_start_values( for (ci, dual_start) in constraint_dual set_dual_start_value(ci, dual_start) end + MOI.set(model, ObjectiveDualStart(), -1.0) + MOI.set(model, ObjectiveSlackGapPrimalStart(), 0.0) return end