From 831efd588e3b8ca15d715eb8afcdbb14c92cdde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 Aug 2019 00:29:01 +0200 Subject: [PATCH 1/2] Update bridge optimizers with variable bridges --- .../Constraint/single_bridge_optimizer.jl | 37 +- src/Bridges/Constraint/slack.jl | 2 + src/Bridges/Variable/Variable.jl | 3 + .../Variable/single_bridge_optimizer.jl | 50 + src/Bridges/bridge_optimizer.jl | 958 ++++++++++++++---- src/Bridges/lazy_bridge_optimizer.jl | 276 ++++- test/Bridges/lazy_bridge_optimizer.jl | 7 +- test/Bridges/utilities.jl | 3 +- 8 files changed, 1044 insertions(+), 292 deletions(-) create mode 100644 src/Bridges/Variable/single_bridge_optimizer.jl diff --git a/src/Bridges/Constraint/single_bridge_optimizer.jl b/src/Bridges/Constraint/single_bridge_optimizer.jl index d7aa70a65d..2730addf5f 100644 --- a/src/Bridges/Constraint/single_bridge_optimizer.jl +++ b/src/Bridges/Constraint/single_bridge_optimizer.jl @@ -8,30 +8,37 @@ even if they are supported by one of its bridges. """ mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer model::OT - con_to_name::Dict{CI, String} + map::Map # index of bridged constraint -> constraint bridge + con_to_name::Dict{MOI.ConstraintIndex, String} name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} - # Constraint Index of bridged constraint -> Bridge. - # It is set to `nothing` when the constraint is deleted. - bridges::Vector{Union{Nothing, AbstractBridge}} - # Constraint Index of bridged constraint -> Constraint type. - constraint_types::Vector{Tuple{DataType, DataType}} - # For `SingleVariable` constraints: (variable, set type) -> bridge - single_variable_constraints::Dict{Tuple{Int64, DataType}, AbstractBridge} end function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} SingleBridgeOptimizer{BT, OT}( - model, Dict{CI, String}(), nothing, - Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[], - Dict{Tuple{Int64, DataType}, AbstractBridge}()) + model, Map(), Dict{MOI.ConstraintIndex, String}(), nothing) end -function MOIB.supports_bridging_constraint(b::SingleBridgeOptimizer{BT}, - F::Type{<:MOI.AbstractFunction}, - S::Type{<:MOI.AbstractSet}) where BT +function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer) + return EmptyMap() +end +function bridges(bridge::SingleBridgeOptimizer) + return bridge.map +end + +MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = true +function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet}) + return false +end +function MOIB.supports_bridging_constraint( + ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) where BT return MOI.supports_constraint(BT, F, S) end function MOIB.is_bridged(b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) return MOIB.supports_bridging_constraint(b, F, S) end -MOIB.bridge_type(b::SingleBridgeOptimizer{BT}, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) where BT = BT +function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, + ::Type{<:MOI.AbstractFunction}, + ::Type{<:MOI.AbstractSet}) where BT + return BT +end diff --git a/src/Bridges/Constraint/slack.jl b/src/Bridges/Constraint/slack.jl index 76fb7f6f8f..0d3dacb7e2 100644 --- a/src/Bridges/Constraint/slack.jl +++ b/src/Bridges/Constraint/slack.jl @@ -36,6 +36,7 @@ MOI.supports_constraint(::Type{ScalarSlackBridge{T}}, MOI.supports_constraint(::Type{ScalarSlackBridge{T}}, ::Type{<:MOI.AbstractScalarFunction}, ::Type{<:MOI.EqualTo}) where {T} = false +MOIB.added_constrained_variable_types(::Type{<:ScalarSlackBridge}) = Tuple{DataType}[] function MOIB.added_constraint_types(::Type{ScalarSlackBridge{T, F, S}}) where {T, F, S} return [(F, MOI.EqualTo{T}), (MOI.SingleVariable, S)] end @@ -137,6 +138,7 @@ MOI.supports_constraint(::Type{VectorSlackBridge{T}}, MOI.supports_constraint(::Type{VectorSlackBridge{T}}, ::Type{<:MOI.VectorOfVariables}, ::Type{<:MOI.AbstractVectorSet}) where {T} = false +MOIB.added_constrained_variable_types(::Type{<:VectorSlackBridge}) = Tuple{DataType}[] function MOIB.added_constraint_types(::Type{VectorSlackBridge{T, F, S}}) where {T, F<:MOI.AbstractVectorFunction, S} return [(F, MOI.Zeros), (MOI.VectorOfVariables, S)] end diff --git a/src/Bridges/Variable/Variable.jl b/src/Bridges/Variable/Variable.jl index 5a658f9d06..3f8e68e404 100644 --- a/src/Bridges/Variable/Variable.jl +++ b/src/Bridges/Variable/Variable.jl @@ -11,4 +11,7 @@ include("bridge.jl") # Mapping between variable indices and bridges include("map.jl") +# Bridge optimizer bridging a specific variable bridge +include("single_bridge_optimizer.jl") + end diff --git a/src/Bridges/Variable/single_bridge_optimizer.jl b/src/Bridges/Variable/single_bridge_optimizer.jl new file mode 100644 index 0000000000..669eaecc5a --- /dev/null +++ b/src/Bridges/Variable/single_bridge_optimizer.jl @@ -0,0 +1,50 @@ +""" + SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer + +The `SingleBridgeOptimizer` bridges any constrained variables supported by the +bridge `BT`. This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref) +which only bridges the constrained variables that are unsupported by the internal model, +even if they are supported by one of its bridges. + +!!! note + Two bridge optimizers using variable bridges cannot be used together as both + of them assume that the underlying model only return variable indices with + nonnegative index. +""" +mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer + model::OT + map::Map # index of bridged variable -> variable bridge + var_to_name::Dict{MOI.VariableIndex, String} + name_to_var::Union{Dict{String, MOI.VariableIndex}, Nothing} + con_to_name::Dict{MOI.ConstraintIndex, String} + name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} +end +function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} + SingleBridgeOptimizer{BT, OT}( + model, Map(), + Dict{MOI.VariableIndex, String}(), nothing, + Dict{MOI.ConstraintIndex, String}(), nothing) +end + +function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer) + return EmptyMap() +end +bridges(bridge::SingleBridgeOptimizer) = bridge.map + +MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = false +function MOIB.supports_bridging_constrained_variable( + ::SingleBridgeOptimizer{BT}, S::Type{<:MOI.AbstractSet}) where BT + return supports_constrained_variable(BT, S) +end +function MOIB.is_bridged(b::SingleBridgeOptimizer, S::Type{<:MOI.AbstractSet}) + return MOIB.supports_bridging_constrained_variable(b, S) +end +function MOIB.is_bridged(::SingleBridgeOptimizer, + ::Type{<:MOI.AbstractFunction}, + ::Type{<:MOI.AbstractSet}) + return false +end +function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, + ::Type{<:MOI.AbstractSet}) where BT + return BT +end diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 9c8997f0d7..874ce35889 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -15,31 +15,70 @@ abstract type AbstractBridgeOptimizer <: MOI.AbstractOptimizer end # AbstractBridgeOptimizer interface +function supports_constraint_bridges end + """ is_bridged(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, - S::Type{<:MOI.AbstractSet})::Bool + S::Type{<:MOI.AbstractSet})::Bool Return a `Bool` indicating whether `b` tries to bridge `F`-in-`S` constraints instead of passing it as is to its internal model. + + is_bridged(b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractSet})::Bool + +Return a `Bool` indicating whether `b` tries to bridge constrained variables in +`S` instead of passing it as is to its internal model. """ function is_bridged end -# Syntactic sugar -function is_bridged(b::AbstractBridgeOptimizer, ::Type{CI{F, S}}) where {F, S} + +""" + is_bridged(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) + +Return a `Bool` indicating whether `vi` is bridged. The variable is said to be +bridged if it is a variable of `b` but not a variable of `b.model`. +""" +is_bridged(::AbstractBridgeOptimizer, vi::MOI.VariableIndex) = vi.value < 0 + +""" + is_bridged(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex) + +Return a `Bool` indicating whether `ci` is bridged. The constraint is said to be +bridged if it is a constraint of `b` but not a constraint of `b.model`. +""" +function is_bridged(b::AbstractBridgeOptimizer, + ci::MOI.ConstraintIndex{F, S}) where {F, S} return is_bridged(b, F, S) end -# We don't bridge variables. -is_bridged(b::AbstractBridgeOptimizer, ::Type{MOI.VariableIndex}) = false +function is_bridged(b::AbstractBridgeOptimizer, + ci::MOI.ConstraintIndex{F, S}) where { + F<:Union{MOI.SingleVariable, MOI.VectorOfVariables}, S} + # `ci.value < 0` if it is variable-bridged or force-bridged + return is_bridged(b, F, S) || ci.value < 0 +end """ - supports_bridging_constraint(b::AbstractBridgeOptimizer, - F::Type{<:MOI.AbstractFunction}, - S::Type{<:MOI.AbstractSet})::Bool + supports_bridging_constrained_variable( + ::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractSet}) + +Return a `Bool` indicating whether `b` supports bridging constrained variable in +`S`. +""" +function supports_bridging_constrained_variable( + ::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractSet}) + return false +end + +""" + supports_bridging_constraint( + b::AbstractBridgeOptimizer, + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet})::Bool Return a `Bool` indicating whether `b` supports bridging `F`-in-`S` constraints. """ -function supports_bridging_constraint(::AbstractBridgeOptimizer, - ::Type{<:MOI.AbstractFunction}, - ::Type{<:MOI.AbstractSet}) +function supports_bridging_constraint( + ::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractFunction}, + ::Type{<:MOI.AbstractSet}) return false end @@ -54,16 +93,45 @@ This function should only be called if `is_bridged(b, F, S)`. function bridge_type end """ - bridge(b::AbstractBridgeOptimizer, ci::CI) + is_variable_bridged(b::AbstractBridgeOptimizer, + ci::MOI.ConstraintIndex) + +Returns whether `ci` is the constraint of a bridged constrained variable. That +is, if it was returned by `Variable.add_key_for_bridge` or +`Variable.add_keys_for_bridge`. Note that it is note equivalent to +`ci.value < 0` as, it can also simply be a constraint on a bridged variable. +""" +is_variable_bridged(::AbstractBridgeOptimizer, ::MOI.ConstraintIndex) = false +function is_variable_bridged( + b::AbstractBridgeOptimizer, + ci::MOI.ConstraintIndex{<:Union{MOI.SingleVariable, MOI.VectorOfVariables}}) + # It can be a constraint corresponding to bridged constrained variables so + # we `check` with `haskey(Constraint.bridges(b), ci)` whether this is the + # case. + return ci.value < 0 && !haskey(Constraint.bridges(b), ci) +end + +""" + bridge(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) + +Return the `Variable.AbstractBridge` used to bridge the variable with index +`vi`. +""" +function bridge(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) + return Variable.bridges(b)[vi] +end + +""" + bridge(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex) Return the `AbstractBridge` used to bridge the constraint with index `ci`. """ -bridge(b::AbstractBridgeOptimizer, ci::CI) = b.bridges[ci.value] -# By convention, they should be stored in a `bridges` field using a -# dictionary-like object. -function bridge(b::AbstractBridgeOptimizer, - ci::CI{MOI.SingleVariable, S}) where S - return b.single_variable_constraints[(ci.value, S)] +function bridge(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex) + if is_variable_bridged(b, ci) + return bridge(b, MOI.VariableIndex(ci.value)) + else + return Constraint.bridges(b)[ci] + end end # Implementation of the MOI interface for AbstractBridgeOptimizer @@ -72,16 +140,22 @@ MOI.optimize!(b::AbstractBridgeOptimizer) = MOI.optimize!(b.model) # By convention, the model should be stored in a `model` field function MOI.is_empty(b::AbstractBridgeOptimizer) - return isempty(b.bridges) && isempty(b.constraint_types) && - MOI.is_empty(b.model) && isempty(b.single_variable_constraints) + return isempty(Variable.bridges(b)) && isempty(Constraint.bridges(b)) && + MOI.is_empty(b.model) end function MOI.empty!(b::AbstractBridgeOptimizer) MOI.empty!(b.model) - empty!(b.bridges) - empty!(b.constraint_types) - empty!(b.single_variable_constraints) - empty!(b.con_to_name) - b.name_to_con = nothing + if Variable.has_bridges(Variable.bridges(b)) + empty!(b.var_to_name) + b.name_to_var = nothing + end + if Variable.has_bridges(Variable.bridges(b)) || + Constraint.has_bridges(Constraint.bridges(b)) + empty!(b.con_to_name) + b.name_to_con = nothing + end + empty!(Variable.bridges(b)) + empty!(Constraint.bridges(b)) end function MOI.supports(b::AbstractBridgeOptimizer, attr::Union{MOI.AbstractModelAttribute, @@ -98,79 +172,175 @@ function MOIU.supports_default_copy_to(b::AbstractBridgeOptimizer, end # References -function _constraint_index(b::AbstractBridgeOptimizer, i::Integer) - F, S = b.constraint_types[i] - return MOI.ConstraintIndex{F, S}(i) +function MOI.is_valid(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) + if is_bridged(b, vi) + return haskey(Variable.bridges(b), vi) + else + return MOI.is_valid(b.model, vi) + end end -MOI.is_valid(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) = MOI.is_valid(b.model, vi) -function MOI.is_valid(b::AbstractBridgeOptimizer, ci::CI{F, S}) where {F, S} - if is_bridged(b, typeof(ci)) - return 1 ≤ ci.value ≤ length(b.bridges) && b.bridges[ci.value] !== nothing && - (F, S) == b.constraint_types[ci.value] +function MOI.is_valid(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex{F, S}) where {F, S} + if is_bridged(b, ci) + if is_variable_bridged(b, ci) + vi = MOI.VariableIndex(ci.value) + return MOI.is_valid(b, vi) && + Variable.constrained_set(Variable.bridges(b), vi) == S + else + return haskey(Constraint.bridges(b), ci) + end else return MOI.is_valid(b.model, ci) end end -function MOI.is_valid(b::AbstractBridgeOptimizer, - ci::CI{MOI.SingleVariable, S}) where S - if is_bridged(b, typeof(ci)) - return haskey(b.single_variable_constraints, (ci.value, S)) +function MOI.delete(b::AbstractBridgeOptimizer, vis::Vector{MOI.VariableIndex}) + if Constraint.has_bridges(Constraint.bridges(b)) + # Delete all `MOI.VectorOfVariables` constraints of these variables. + for ci in Constraint.variable_constraints(Constraint.bridges(b), vis) + if vis == MOI.get(b, MOI.ConstraintFunction(), ci).variables + MOI.delete(b, ci) + end + end + # Delete all `MOI.SingleVariable` constraints of these variables. + for vi in vis + for ci in Constraint.variable_constraints(Constraint.bridges(b), vi) + MOI.delete(b, ci) + end + end + end + if any(vi -> is_bridged(b, vi), vis) + for vi in vis + MOI.throw_if_not_valid(b, vi) + end + if all(vi -> is_bridged(b, vi), vis) && Variable.has_keys(Variable.bridges(b), vis) + MOI.delete(b, bridge(b, first(vis))) + b.name_to_var = nothing + for vi in vis + delete!(b.var_to_name, vi) + end + ci = Variable.constraint(Variable.bridges(b), first(vis)) + b.name_to_con = nothing + delete!(b.con_to_name, ci) + delete!(Variable.bridges(b), vis) + else + for vi in vis + MOI.delete(b, vi) + end + end else - return MOI.is_valid(b.model, ci) + MOI.delete(b.model, vis) end end function MOI.delete(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) - # Delete all `MOI.SingleVariable` constraint of this variable - for key in keys(b.single_variable_constraints) - if key[1] == vi.value - MOI.delete(b, MOI.ConstraintIndex{MOI.SingleVariable, - key[2]}(vi.value)) + if Constraint.has_bridges(Constraint.bridges(b)) + # Delete all `MOI.SingleVariable` constraint of this variable + for ci in Constraint.variable_constraints(Constraint.bridges(b), vi) + MOI.delete(b, ci) end end - MOI.delete(b.model, vi) -end -function _remove_bridge(b, ci) - b.bridges[ci.value] = nothing -end -function _remove_bridge(b, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S - delete!(b.single_variable_constraints, (ci.value, S)) + if is_bridged(b, vi) + MOI.throw_if_not_valid(b, vi) + if Variable.length_of_vector_of_variables(Variable.bridges(b), vi) > 1 + if MOI.supports_dimension_update(Variable.constrained_set(Variable.bridges(b), vi)) + MOI.delete(b, bridge(b, vi), _index(b, vi)...) + else + MOIU.throw_delete_variable_in_vov(vi) + end + else + MOI.delete(b, bridge(b, vi)) + ci = Variable.constraint(Variable.bridges(b), vi) + b.name_to_con = nothing + delete!(b.con_to_name, ci) + end + delete!(Variable.bridges(b), vi) + b.name_to_var = nothing + delete!(b.var_to_name, vi) + else + MOI.delete(b.model, vi) + end end -function MOI.delete(b::AbstractBridgeOptimizer, ci::CI) - if is_bridged(b, typeof(ci)) - if !MOI.is_valid(b, ci) - throw(MOI.InvalidIndex(ci)) +function MOI.delete(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex) + if is_bridged(b, ci) + MOI.throw_if_not_valid(b, ci) + br = bridge(b, ci) + if is_variable_bridged(b, ci) + error("Cannot delete constraint index of bridged constrained", + " variables. Delete the scalar variable or the vector of", + " variables instead.") + else + delete!(Constraint.bridges(b), ci) end - MOI.delete(b, bridge(b, ci)) - _remove_bridge(b, ci) + MOI.delete(b, br) b.name_to_con = nothing - if haskey(b.con_to_name, ci) - delete!(b.con_to_name, ci) - end + delete!(b.con_to_name, ci) else MOI.delete(b.model, ci) end end # Attributes -# List of indices of all constraints, including those bridged -function get_all_including_bridged( + +""" + function reduce_bridged( + b::AbstractBridgeOptimizer, args, + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}, + init, operate_variable_bridges!, + operate_constraint_bridges!) + +If `F`-in-`S` constraints may be added to `b.model`, +starts with `value = MOI.get(b.model, args...)`, otherwise, starts with +`value = init()`. Then +* if `F`-in-`S` constraints may correspond to + bridged variables, modify it with `operate_variable_bridges!`; +* if `F`-in-`S` constraints may correspond to + bridged constraints, modify it with `operate_constraint_bridges!`; +then return the final `value`. +""" +function reduce_bridged( b::AbstractBridgeOptimizer, - attr::MOI.ListOfConstraintIndices{F, S}) where {F, S} + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}, + init, model_value, + operate_variable_bridges!, + operate_constraint_bridges!) if is_bridged(b, F, S) - return MOI.ConstraintIndex{F, S}[MOI.ConstraintIndex{F, S}(i) for i in eachindex(b.bridges) if MOI.is_valid(b, MOI.ConstraintIndex{F, S}(i))] + value = init() else - return MOI.get(b.model, attr) + value = model_value() + end + variable_function = F == variable_function_type(S) + if variable_function && is_bridged(b, S) + value = operate_variable_bridges!(value) + end + # Even it it is not bridged, it may have been force-bridged because one of the + # variable in the function was bridged. + if is_bridged(b, F, S) || (variable_function && supports_constraint_bridges(b)) + value = operate_constraint_bridges!(value) end + return value end + +# List of indices of all constraints, including those bridged function get_all_including_bridged( b::AbstractBridgeOptimizer, - attr::MOI.ListOfConstraintIndices{MOI.SingleVariable, S}) where S - F = MOI.SingleVariable - if is_bridged(b, F, S) - return MOI.ConstraintIndex{F, S}[MOI.ConstraintIndex{F, S}(key[1]) for key in keys(b.single_variable_constraints) if key[2] == S] - else - return MOI.get(b.model, attr) + attr::MOI.ListOfVariableIndices) + list = MOI.get(b.model, attr) + if !isempty(Variable.bridges(b)) + list = append!(copy(list), keys(Variable.bridges(b))) end + return list +end +function get_all_including_bridged( + b::AbstractBridgeOptimizer, + attr::MOI.ListOfConstraintIndices{F, S}) where {F, S} + return reduce_bridged( + b, F, S, () -> MOI.ConstraintIndex{F, S}[], + () -> MOI.get(b.model, attr), + list -> append!(list, Variable.constraints_with_set( + Variable.bridges(b), S)), + list -> append!(list, Constraint.keys_of_type( + Constraint.bridges(b), MOI.ConstraintIndex{F, S})) + ) end # Remove constraints bridged by `bridge` from `list` function _remove_bridged(list, bridge, attr) @@ -182,80 +352,75 @@ function _remove_bridged(list, bridge, attr) end end function MOI.get(b::AbstractBridgeOptimizer, - attr::MOI.ListOfConstraintIndices{F, S}) where {F, S} + attr::Union{MOI.ListOfConstraintIndices, + MOI.ListOfVariableIndices}) list = get_all_including_bridged(b, attr) - for bridge in b.bridges - if bridge !== nothing - _remove_bridged(list, bridge, attr) - end + for bridge in values(Variable.bridges(b)) + _remove_bridged(list, bridge, attr) end - for bridge in values(b.single_variable_constraints) + for bridge in values(Constraint.bridges(b)) _remove_bridged(list, bridge, attr) end return list end -function _number_of(b::AbstractBridgeOptimizer, model::MOI.ModelLike, - attr::Union{MOI.NumberOfConstraints, MOI.NumberOfVariables}) - s = MOI.get(model, attr) - for bridge in b.bridges - if bridge !== nothing - s -= MOI.get(bridge, attr) - end +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfVariables) + s = MOI.get(b.model, attr) + Variable.number_of_variables(Variable.bridges(b)) + for bridge in values(Variable.bridges(b)) + s -= MOI.get(bridge, attr) end - for bridge in values(b.single_variable_constraints) + for bridge in values(Constraint.bridges(b)) s -= MOI.get(bridge, attr) end return s end -function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfVariables) - return _number_of(b, b.model, attr) -end # Number of all constraints, including those bridged function get_all_including_bridged( b::AbstractBridgeOptimizer, attr::MOI.NumberOfConstraints{F, S}) where {F, S} - if is_bridged(b, F, S) - return count(i -> MOI.is_valid(b, MOI.ConstraintIndex{F, S}(i)), eachindex(b.bridges)) - else - return MOI.get(b.model, attr) - end -end -function get_all_including_bridged( - b::AbstractBridgeOptimizer, - attr::MOI.NumberOfConstraints{MOI.SingleVariable, S}) where S - if is_bridged(b, MOI.SingleVariable, S) - return count(key -> key[2] == S, keys(b.single_variable_constraints)) - else - return MOI.get(b.model, attr) - end + return reduce_bridged( + b, F, S, () -> 0, () -> MOI.get(b.model, attr), + num -> num + Variable.number_with_set(Variable.bridges(b), S), + num -> num + Constraint.number_of_type(Constraint.bridges(b), + MOI.ConstraintIndex{F, S}) + ) end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfConstraints{F, S}) where {F, S} s = get_all_including_bridged(b, attr) # The constraints counted in `s` may have been added by bridges - for bridge in b.bridges - if bridge !== nothing - s -= MOI.get(bridge, attr) - end + for bridge in values(Variable.bridges(b)) + s -= MOI.get(bridge, attr) end - for bridge in values(b.single_variable_constraints) + for bridge in values(Constraint.bridges(b)) s -= MOI.get(bridge, attr) end return s end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraints) - list_of_types = MOI.get(b.model, attr) - list_of_bridged_types = Set{Tuple{DataType, DataType}}() - for i in eachindex(b.bridges) - if b.bridges[i] !== nothing - push!(list_of_bridged_types, b.constraint_types[i]) - end - end - for key in keys(b.single_variable_constraints) - push!(list_of_bridged_types, (MOI.SingleVariable, key[2])) + if Constraint.has_bridges(Constraint.bridges(b)) && Variable.has_bridges(Variable.bridges(b)) + set_of_types = Constraint.list_of_key_types(Constraint.bridges(b)) + union!(set_of_types, Variable.list_of_constraint_types(Variable.bridges(b))) + # There may be types already in `list_of_types` of a supported constraint + # was force-bridged because a variable in the `SingleVariable` or + # `VectorOfVariables` function was bridged even though the constraint type + # is supported by `b.model`. As `set_of_types` is a set, these duplicates + # are merge automatically. + union!(set_of_types, MOI.get(b.model, attr)) + list_of_types = collect(set_of_types) + elseif Constraint.has_bridges(Constraint.bridges(b)) + # There should be no duplicate so no need to do `Set` union. + list_of_types = [ + MOI.get(b.model, attr); + collect(Constraint.list_of_key_types(Constraint.bridges(b))) + ] + elseif Variable.has_bridges(Variable.bridges(b)) + set_of_types = Variable.list_of_constraint_types(Variable.bridges(b)) + union!(set_of_types, MOI.get(b.model, attr)) + list_of_types = collect(set_of_types) + else + list_of_types = copy(MOI.get(b.model, attr)) end - list_of_types = [list_of_types; collect(list_of_bridged_types)] # Some constraint types show up in `list_of_types` including when all the # constraints of that type have been created by bridges and not by the user. # The code in `NumberOfConstraints` takes care of removing these constraints @@ -270,66 +435,138 @@ end function MOI.get(b::AbstractBridgeOptimizer, attr::Union{MOI.AbstractModelAttribute, MOI.AbstractOptimizerAttribute}) - return MOI.get(b.model, attr) + return unbridged_function(b, MOI.get(b.model, attr)) end function MOI.set(b::AbstractBridgeOptimizer, - attr::Union{MOI.AbstractModelAttribute, - MOI.AbstractOptimizerAttribute}, - value) - return MOI.set(b.model, attr, value) + attr::Union{MOI.AbstractModelAttribute, + MOI.AbstractOptimizerAttribute}, + value) + return MOI.set(b.model, attr, bridged_function(b, value)) end + # Variable attributes +function _index(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) + i = Variable.index_in_vector_of_variables(Variable.bridges(b), vi) + if iszero(i.value) + return tuple() + else + return (i,) + end +end + function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.AbstractVariableAttribute, index::MOI.VariableIndex) - return MOI.get(b.model, attr, index) + if is_bridged(b, index) + return MOI.get(b, attr, bridge(b, index), _index(b, index)...) + else + return MOI.get(b.model, attr, index) + end end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.AbstractVariableAttribute, indices::Vector{MOI.VariableIndex}) - return MOI.get(b.model, attr, indices) + if any(index -> is_bridged(b, index), indices) + return MOI.get.(b, attr, indices) + else + return MOI.get(b.model, attr, indices) + end end function MOI.supports(b::AbstractBridgeOptimizer, attr::MOI.AbstractVariableAttribute, - IndexType::Type{<:MOI.Index}) - return MOI.supports(b.model, attr, IndexType) + ::Type{MOI.VariableIndex}) + return MOI.supports(b.model, attr, MOI.VariableIndex) end function MOI.set(b::AbstractBridgeOptimizer, - attr::MOI.AbstractVariableAttribute, - index::MOI.Index, value) - return MOI.set(b.model, attr, index, value) + attr::MOI.AbstractVariableAttribute, + index::MOI.Index, value) + if is_bridged(b, index) + return MOI.set(b, attr, bridge(b, index), value, _index(b, index)...) + else + return MOI.set(b.model, attr, index, value) + end end function MOI.set(b::AbstractBridgeOptimizer, - attr::MOI.AbstractVariableAttribute, - indices::Vector{<:MOI.Index}, values::Vector) - return MOI.set(b.model, attr, indices, values) + attr::MOI.AbstractVariableAttribute, + indices::Vector{<:MOI.Index}, values::Vector) + if any(index -> is_bridged(b, index), indices) + return MOI.set.(b, attr, indices, values) + else + return MOI.set(b.model, attr, indices, values) + end end # Constraint attributes +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex) + if is_bridged(b, ci) + MOI.throw_if_not_valid(b, ci) + br = bridge(b, ci) + if br isa Variable.AbstractBridge + return Variable.function_for(Variable.bridges(b), ci) + end + func = MOI.get(b, attr, br) + else + func = MOI.get(b.model, attr, ci) + end + return unbridged_constraint_function(b, func) +end +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{<:Union{MOI.AbstractVectorFunction, + MOI.SingleVariable}}) + return if is_bridged(b, ci) + MOI.throw_if_not_valid(b, ci) + MOI.get(b, attr, bridge(b, ci)) + else + MOI.get(b.model, attr, ci) + end +end +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction}) + if is_bridged(b, ci) + MOI.throw_if_not_valid(b, ci) + set = MOI.get(b, attr, bridge(b, ci)) + else + set = MOI.get(b.model, attr, ci) + end + if Variable.has_bridges(Variable.bridges(b)) + # The function constant of the bridged function was moved to the set, + # we need to remove it. + if is_bridged(b, ci) + func = MOI.get(b, MOI.ConstraintFunction(), bridge(b, ci)) + else + func = MOI.get(b.model, MOI.ConstraintFunction(), ci) + end + f = unbridged_function(b, func) + set = MOIU.shift_constant(set, -MOI.constant(f)) + end + return set +end function MOI.get(b::AbstractBridgeOptimizer, - attr::MOI.AbstractConstraintAttribute, ci::CI) - if is_bridged(b, typeof(ci)) + attr::MOI.AbstractConstraintAttribute, ci::MOI.ConstraintIndex) + if is_bridged(b, ci) MOI.throw_if_not_valid(b, ci) - return MOI.get(b, attr, bridge(b, ci)) + func = MOI.get(b, attr, bridge(b, ci)) else - return MOI.get(b.model, attr, ci) + func = MOI.get(b.model, attr, ci) end + return unbridged_function(b, func) end function MOI.supports(b::AbstractBridgeOptimizer, attr::MOI.AbstractConstraintAttribute, - IndexType::Type{CI{F, S}}) where {F, S} - if is_bridged(b, IndexType) - return MOI.supports(b, attr, Constraint.concrete_bridge_type(b, F, S)) - else - return MOI.supports(b.model, attr, IndexType) - end + IndexType::Type{MOI.ConstraintIndex{F, S}}) where {F, S} + return reduce_bridged( + b, F, S, () -> true, () -> MOI.supports(b.model, attr, IndexType), + ok -> ok && MOI.supports(b, attr, Variable.concrete_bridge_type(b, S)), + ok -> ok && MOI.supports(b, attr, Constraint.concrete_bridge_type(b, F, S)) + ) end function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.AbstractConstraintAttribute, - index::CI, value) - if is_bridged(b, typeof(index)) + index::MOI.ConstraintIndex, value) + if is_bridged(b, index) MOI.throw_if_not_valid(b, index) return MOI.set(b, attr, bridge(b, index), value) else @@ -337,26 +574,39 @@ function MOI.set(b::AbstractBridgeOptimizer, end end ## Getting and Setting names +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.VariableName, + vi::MOI.VariableIndex) + if is_bridged(b, vi) + return get(b.var_to_name, vi, MOIU.EMPTYSTRING) + else + return MOI.get(b.model, attr, vi) + end +end +function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.VariableName, + vi::MOI.VariableIndex, name::String) + if is_bridged(b, vi) + b.var_to_name[vi] = name + b.name_to_var = nothing # Invalidate the name map. + else + MOI.set(b.model, attr, vi, name) + end +end + function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, - constraint_index::CI) - if is_bridged(b, typeof(constraint_index)) + constraint_index::MOI.ConstraintIndex) + if is_bridged(b, constraint_index) return get(b.con_to_name, constraint_index, MOIU.EMPTYSTRING) else return MOI.get(b.model, attr, constraint_index) end end - function MOI.supports(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, - Index::Type{CI{F,S}}) where {F,S} - if is_bridged(b, Index) - return true - else - return MOI.supports(b.model, attr, Index) - end + Index::Type{MOI.ConstraintIndex{F,S}}) where {F,S} + return is_bridged(b, F, S) || MOI.supports(b.model, attr, Index) end function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, - constraint_index::CI, name::String) - if is_bridged(b, typeof(constraint_index)) + constraint_index::MOI.ConstraintIndex, name::String) + if is_bridged(b, constraint_index) b.con_to_name[constraint_index] = name b.name_to_con = nothing # Invalidate the name map. else @@ -364,87 +614,115 @@ function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, end end -# Name (similar to `UniversalFallback`) -function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{MOI.VariableIndex}, +# Query index from name (similar to `UniversalFallback`) +function MOI.get(b::AbstractBridgeOptimizer, ::Type{MOI.VariableIndex}, name::String) - return MOI.get(b.model, IdxT, name) + vi = MOI.get(b.model, MOI.VariableIndex, name) + if Variable.has_bridges(Variable.bridges(b)) + if b.name_to_var === nothing + b.name_to_var = MOIU.build_name_to_var_map(b.var_to_name) + end + vi_bridged = get(b.name_to_var, name, nothing) + MOIU.throw_if_multiple_with_name(vi_bridged, name) + return MOIU.check_type_and_multiple_names( + MOI.VariableIndex, vi_bridged, vi, name) + else + return vi + end end - -function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{<:MOI.ConstraintIndex}, - name::String) +function MOI.get(b::AbstractBridgeOptimizer, + IdxT::Type{MOI.ConstraintIndex{F, S}}, + name::String) where {F, S} if b.name_to_con === nothing b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) end - - if is_bridged(b, IdxT) - ci = get(b.name_to_con, name, nothing) - if ci == CI{Nothing, Nothing}(-1) - error("Multiple constraints have the name $name.") - elseif ci isa IdxT - if MOI.get(b.model, CI, name) !== nothing - error("Multiple constraints have the name $name.") - end - return ci - else - return nothing - end + if is_bridged(b, F, S) + # There is no `F`-in-`S` constraint in `b.model`, `ci` is only got + # to check for duplicate names. + ci = MOI.get(b.model, MOI.ConstraintIndex, name) else ci = MOI.get(b.model, IdxT, name) - if ci !== nothing && haskey(b.name_to_con, name) - error("Multiple constraints have the name $name.") - end - return ci end + ci_bridged = get(b.name_to_con, name, nothing) + MOIU.throw_if_multiple_with_name(ci_bridged, name) + return MOIU.check_type_and_multiple_names(IdxT, ci_bridged, ci, name) end - -# We have no information as to whether the constraint is in the bridge or the -# model. Therefore, we try the model first, and then the bridge if that fails. -function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{CI}, +function MOI.get(b::AbstractBridgeOptimizer, + IdxT::Type{MOI.ConstraintIndex}, name::String) if b.name_to_con === nothing b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) end - - ci = MOI.get(b.model, IdxT, name) - if ci === nothing - b_ci = get(b.name_to_con, name, nothing) - if b_ci == CI{Nothing, Nothing}(-1) - error("Multiple constraints have the name $name.") - else - return b_ci - end - else - if haskey(b.name_to_con, name) - error("Multiple constraints have the name $name.") - end - - return ci - end + ci_bridged = get(b.name_to_con, name, nothing) + MOIU.throw_if_multiple_with_name(ci_bridged, name) + return MOIU.check_type_and_multiple_names( + IdxT, ci_bridged, MOI.get(b.model, IdxT, name), name) end # Constraints function MOI.supports_constraint(b::AbstractBridgeOptimizer, - F::Type{<:MOI.AbstractFunction}, - S::Type{<:MOI.AbstractSet}) + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) if is_bridged(b, F, S) + if F == variable_function_type(S) && + supports_bridging_constrained_variable(b, S) + return true + end return supports_bridging_constraint(b, F, S) else return MOI.supports_constraint(b.model, F, S) end end -function store_bridge(b::AbstractBridgeOptimizer, func::MOI.SingleVariable, - set::MOI.AbstractSet, bridge) - b.single_variable_constraints[(func.variable.value, typeof(set))] = bridge - return MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}(func.variable.value) -end -function store_bridge(b::AbstractBridgeOptimizer, func::MOI.AbstractFunction, - set::MOI.AbstractSet, bridge) - push!(b.bridges, bridge) - push!(b.constraint_types, (typeof(func), typeof(set))) - return MOI.ConstraintIndex{typeof(func), typeof(set)}(length(b.bridges)) -end function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, s::MOI.AbstractSet) + if Variable.has_bridges(Variable.bridges(b)) + if f isa MOI.SingleVariable + if is_bridged(b, f.variable) + if MOI.is_valid(b, MOI.ConstraintIndex{MOI.SingleVariable, typeof(s)}(f.variable.value)) + # The other constraint could have been through a variable bridge. + error("Cannot add two `SingleVariable`-in-`$(typeof(s))`", + " on the same variable $(f.variable).") + end + BridgeType = Constraint.concrete_bridge_type( + Constraint.ScalarFunctionizeBridge{Float64}, typeof(f), typeof(s)) + bridge = Constraint.bridge_constraint(BridgeType, b, f, s) + return Constraint.add_key_for_bridge(Constraint.bridges(b), bridge, f, s) + end + elseif f isa MOI.VectorOfVariables + if any(vi -> is_bridged(b, vi), f.variables) + if MOI.is_valid(b, MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(s)}(first(f.variables).value)) + # The other constraint could have been through a variable bridge. + error("Cannot add two `VectorOfVariables`-in-`$(typeof(s))`", + " on the same first variable $(first(f.variables)).") + end + if !is_bridged(b, first(f.variables)) && !is_bridged(b, typeof(f), typeof(s)) + # The index of the contraint will have positive value hence + # it would clash with the index space of `b.model` since + # the constraint type is normally not bridged. + error("Cannot `VectorOfVariables`-in-`$(typeof(s))` for", + " which some variables are bridged but not the", + " first one `$(first(f.variables))`.") + end + BridgeType = Constraint.concrete_bridge_type( + Constraint.VectorFunctionizeBridge{Float64}, typeof(f), typeof(s)) + bridge = Constraint.bridge_constraint(BridgeType, b, f, s) + return Constraint.add_key_for_bridge(Constraint.bridges(b), bridge, f, s) + end + else + if f isa MOI.AbstractScalarFunction + constant = MOI.constant(f) + if !iszero(constant) + # We use the fact that the initial function constant was zero to + # implement getters for `MOI.ConstraintFunction` and + # `MOI.ConstraintSet`. + throw(MOI.ScalarFunctionConstantNotZero{ + typeof(constant), typeof(f), typeof(s)}(constant)) + end + end + f = bridged_function(b, f)::typeof(f) + f, s = MOIU.normalize_constant(f, s) + end + end if is_bridged(b, typeof(f), typeof(s)) # We compute `BridgeType` first as `concrete_bridge_type` calls # `bridge_type` which might throw an `UnsupportedConstraint` error in @@ -452,7 +730,8 @@ function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, BridgeType = Constraint.concrete_bridge_type(b, typeof(f), typeof(s)) # `add_constraint` might throw an `UnsupportedConstraint` but no # modification has been done in the previous line - return store_bridge(b, f, s, Constraint.bridge_constraint(BridgeType, b, f, s)) + bridge = Constraint.bridge_constraint(BridgeType, b, f, s) + return Constraint.add_key_for_bridge(Constraint.bridges(b), bridge, f, s) else return MOI.add_constraint(b.model, f, s) end @@ -463,26 +742,267 @@ function MOI.add_constraints(b::AbstractBridgeOptimizer, f::Vector{F}, if is_bridged(b, F, S) return MOI.add_constraint.(b, f, s) else + if Variable.has_bridges(Variable.bridges(b)) + if S == MOI.SingleVariable + if any(func -> is_bridged(b, func.variable), f) + return MOI.add_constraint.(b, f, s) + end + elseif S == MOI.VectorOfVariables + if any(func -> any(vi -> is_bridged(b, vi), func.variables), f) + return MOI.add_constraint.(b, f, s) + end + else + f = F[bridged_function(b, func)::F for func in f] + end + end return MOI.add_constraints(b.model, f, s) end end -function MOI.modify(b::AbstractBridgeOptimizer, ci::CI, - change::MOI.AbstractFunctionModification) - if is_bridged(b, typeof(ci)) - MOI.modify(b, bridge(b, ci), change) +function is_bridged(::AbstractBridgeOptimizer, + ::Union{MOI.ScalarConstantChange, MOI.VectorConstantChange}) + return false +end +function is_bridged(b::AbstractBridgeOptimizer, + change::Union{MOI.ScalarCoefficientChange, MOI.MultirowChange}) + return is_bridged(b, change.variable) +end +function modify_bridged_change(b::AbstractBridgeOptimizer, obj, + change::MOI.MultirowChange) + func = bridged_variable_function(b, change.variable)::MOI.ScalarAffineFunction + if !iszero(func.constant) + # We would need to get the constant in the function, and the + # coefficient of `change.variable` to remove its contribution + # to the constant and then modify the constant. + MOI.throw_modify_not_allowed( + obj, change, "The change $change contains variables bridged into" * + " a function with nonzero constant.") + end + for t in func.terms + coefs = [(i, coef * t.coefficient) for (i, coef) in change.new_coefficients] + MOI.modify(b, obj, MOI.MultirowChange(t.variable_index, coefs)) + end +end +function modify_bridged_change(b::AbstractBridgeOptimizer, obj, + change::MOI.ScalarCoefficientChange) + func = bridged_variable_function(b, change.variable)::MOI.ScalarAffineFunction + if !iszero(func.constant) + # We would need to get the constant in the set, and the + # coefficient of `change.variable` to remove its contribution + # to the constant and then modify the constant. + MOI.throw_modify_not_allowed( + obj, change, "The change $change contains variables bridged into" * + " a function with nonzero constant.") + end + for t in func.terms + coef = t.coefficient * change.new_coefficient + MOI.modify(b, obj, MOI.ScalarCoefficientChange(t.variable_index, coef)) + end +end +function MOI.modify(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex, + change::MOI.AbstractFunctionModification) + if is_bridged(b, change) + modify_bridged_change(b, ci, change) else - MOI.modify(b.model, ci, change) + if is_bridged(b, ci) + MOI.modify(b, bridge(b, ci), change) + else + MOI.modify(b.model, ci, change) + end end end # Objective function MOI.modify(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) - MOI.modify(b.model, obj, change) + if is_bridged(b, change) + modify_bridged_change(b, obj, change) + else + MOI.modify(b.model, obj, change) + end end # Variables -MOI.add_variable(b::AbstractBridgeOptimizer) = MOI.add_variable(b.model) -MOI.add_variables(b::AbstractBridgeOptimizer, n) = MOI.add_variables(b.model, n) +function MOI.add_variable(b::AbstractBridgeOptimizer) + if is_bridged(b, MOI.Reals) + variables, constraint = MOI.add_constrained_variables(b, MOI.Reals(1)) + @assert isone(length(variables)) + return first(variables) + else + return MOI.add_variable(b.model) + end +end +function MOI.add_variables(b::AbstractBridgeOptimizer, n) + if is_bridged(b, MOI.Reals) + variables, constraint = MOI.add_constrained_variables(b, MOI.Reals(n)) + return variables + else + return MOI.add_variables(b.model, n) + end +end + +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)) + 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) + else + variables = MOI.add_variables(b, MOI.dimension(set)) + constraint = MOI.add_constraint(b, MOI.VectorOfVariables(variables), set) + return variables, constraint + end + else + return MOI.add_constrained_variables(b.model, set) + end +end +function MOI.add_constrained_variable(b::AbstractBridgeOptimizer, + set::MOI.AbstractScalarSet) + if is_bridged(b, typeof(set)) || + is_bridged(b, MOI.SingleVariable, typeof(set)) + if 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_key_for_bridge(Variable.bridges(b), bridge, set) + else + variable = MOI.add_variable(b) + constraint = MOI.add_constraint(b, MOI.SingleVariable(variable), set) + return variable, constraint + end + else + return MOI.add_constrained_variable(b.model, set) + end +end + + +""" + bridged_variable_function(b::AbstractBridgeOptimizer, + vi::MOI.VariableIndex) + +Return a `MOI.AbstractScalarFunction` of variables of `b.model` that equals +`vi`. That is, if the variable `vi` is bridged, it returns its expression in +terms of the variables of `b.model`. Otherwise, it returns +`MOI.SingleVariable(vi)`. +""" +function bridged_variable_function(b::AbstractBridgeOptimizer, + vi::MOI.VariableIndex) + if is_bridged(b, vi) + func = bridged_function(bridge(b, vi), _index(b, vi)...) + # If two variable bridges are chained, `func` may still contain + # bridged variables. + return bridged_function(b, func) + else + return MOI.SingleVariable(vi) + end +end + +""" + bridged_function(b::AbstractBridgeOptimizer, value)::typeof(value) + +Substitute any bridged [`MOI.VariableIndex`](@ref) in `value` by an equivalent +expression in terms of variables of `b.model`. +""" +function bridged_function(bridge::AbstractBridgeOptimizer, value) + if !Variable.has_bridges(Variable.bridges(bridge)) + # Shortcut, this allows performance to be unaltered when no variable + # bridges are used. + return value + end + # We assume that the type of `value` is not altered. This restricts + # variable bridges to only return `ScalarAffineFunction` but otherwise, + # the peformance would be bad. + return MOIU.substitute_variables( + vi -> bridged_variable_function(bridge, vi), value)::typeof(value) +end +function bridged_function(b::AbstractBridgeOptimizer, + func::MOI.SingleVariable) + # Should not be called by `add_constraint` as it force-bridges it + # but could be called by attributes + if is_bridged(b, func.variable) + # It could be solved by force-bridging the attribues (e.g. objective). + error("Using bridged variable in `SingleVariable` function.") + end + return func +end + +""" + unbridged_variable_function(b::AbstractBridgeOptimizer, + vi::MOI.VariableIndex) + +Return a `MOI.AbstractScalarFunction` of variables of `b` that equals `vi`. +That is, if the variable `vi` is an internal variable of `b.model` created by a +but not visible to the user, it returns its expression in terms of the variables +of bridged variables. Otherwise, it returns `MOI.SingleVariable(vi)`. +""" +function unbridged_variable_function(b::AbstractBridgeOptimizer, + vi::MOI.VariableIndex) + func = Variable.unbridged_function(Variable.bridges(b), vi) + if func === nothing + return MOI.SingleVariable(vi) + else + # If two variable bridges are chained, `func` may still contain + # variables to unbridge. + return unbridged_function(b, func) + end +end + +""" + unbridged_function(b::AbstractBridgeOptimizer, value)::typeof(value) + +Substitute any internal [`MOI.VariableIndex`](@ref) of `b.model` created by a +variable bridge but not visible to the user in `value` by an equivalent +expression in terms of bridged variables. +""" +function unbridged_function(b::AbstractBridgeOptimizer, value) + if !Variable.has_bridges(Variable.bridges(b)) + return value + end + # If `value` does not contain any variable, this will never call + # `unbridged_variable_function` hence it might silently return an incorrect + # value so we call `throw_if_cannot_unbridge` here. + Variable.throw_if_cannot_unbridge(Variable.bridges(b)) + return MOIU.substitute_variables( + vi -> unbridged_variable_function(b, vi), value)::typeof(value) +end +function unbridged_function(bridge::AbstractBridgeOptimizer, + func::Union{MOI.SingleVariable, MOI.VectorOfVariables}) + return func # bridged variables are not allowed in non-bridged constraints +end + +""" + unbridged_constraint_function( + b::AbstractBridgeOptimizer, + func::MOI.AbstractFunction + ) + +Similar to `unbridged_function(b, func)` but zero the function constant if it is +scalar. +""" +function unbridged_constraint_function end + +function unbridged_constraint_function( + b::AbstractBridgeOptimizer, + func::Union{MOI.AbstractVectorSet, MOI.SingleVariable} +) + return unbridged_function(b, func) +end +function unbridged_constraint_function(b::AbstractBridgeOptimizer, + func::MOI.AbstractScalarFunction) + if !Variable.has_bridges(Variable.bridges(b)) + return func + end + f = unbridged_function(b, func) + if !iszero(MOI.constant(f)) + f = copy(f) + f.constant = zero(f.constant) + end + return f +end +function unbridged_constraint_function( + ::AbstractBridgeOptimizer, func::MOI.AbstractVectorFunction) + return func +end + # TODO add transform diff --git a/src/Bridges/lazy_bridge_optimizer.jl b/src/Bridges/lazy_bridge_optimizer.jl index 3e131b111a..ee690759b4 100644 --- a/src/Bridges/lazy_bridge_optimizer.jl +++ b/src/Bridges/lazy_bridge_optimizer.jl @@ -10,54 +10,111 @@ bridged into a constraint `F3`-in-`S3` (supported by the internal model) using b it will choose bridge 1 as it allows to bridge `F`-in-`S` using only one bridge instead of two if it uses bridge 2 and 3. """ mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer - model::OT # Internal model - con_to_name::Dict{CI, String} + # Internal model + model::OT + # Bridged variables + variable_map::Variable.Map + var_to_name::Dict{MOI.VariableIndex, String} + name_to_var::Union{Dict{String, MOI.VariableIndex}, Nothing} + # Bridged constraints + constraint_map::Constraint.Map + con_to_name::Dict{MOI.ConstraintIndex, String} name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} - # Constraint Index of bridged constraint -> Bridge. - # It is set to `nothing` when the constraint is deleted. - bridges::Vector{Union{Nothing, Constraint.AbstractBridge}} - # Constraint Index of bridged constraint -> Constraint type. - constraint_types::Vector{Tuple{DataType, DataType}} - # For `SingleVariable` constraints: (variable, set type) -> bridge - single_variable_constraints::Dict{Tuple{Int64, DataType}, Constraint.AbstractBridge} - bridgetypes::Vector{Any} # List of types of available bridges - dist::Dict{Tuple{DataType, DataType}, Int} # (F, S) -> Number of bridges that need to be used for an `F`-in-`S` constraint - best::Dict{Tuple{DataType, DataType}, DataType} # (F, S) -> Bridge to be used for an `F`-in-`S` constraint + # Bellman-Ford + # List of types of available bridges + variable_bridge_types::Vector{Any} + # (S,) -> Number of bridges that need to be used for constrained variables in `S` + variable_dist::Dict{Tuple{DataType}, Int} + # (S,) -> Bridge to be used for constrained variables in `S` + variable_best::Dict{Tuple{DataType}, DataType} + # List of types of available bridges + constraint_bridge_types::Vector{Any} + # (F, S) -> Number of bridges that need to be used for an `F`-in-`S` constraint + 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} end function LazyBridgeOptimizer(model::MOI.ModelLike) return LazyBridgeOptimizer{typeof(model)}( - model, Dict{CI, String}(), nothing, - Union{Nothing, Constraint.AbstractBridge}[], Tuple{DataType, DataType}[], - Dict{Tuple{Int64, DataType}, Constraint.AbstractBridge}(), + model, + Variable.Map(), Dict{MOI.VariableIndex, String}(), nothing, + Constraint.Map(), Dict{MOI.ConstraintIndex, String}(), nothing, + Any[], Dict{Tuple{DataType}, Int}(), + Dict{Tuple{DataType}, DataType}(), Any[], Dict{Tuple{DataType, DataType}, Int}(), Dict{Tuple{DataType, DataType}, DataType}()) end +function Variable.bridges(bridge::LazyBridgeOptimizer) + return bridge.variable_map +end +function Constraint.bridges(bridge::LazyBridgeOptimizer) + return bridge.constraint_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) + return 0 + else + return get(b.variable_dist, (S,), typemax(Int)) + end +end + function _dist(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) if MOI.supports_constraint(b.model, F, S) return 0 else - return get(b.dist, (F, S), typemax(Int)) + return get(b.constraint_dist, (F, S), typemax(Int)) end end -# Update `b.dist` and `b.dest` for constraint types in `constraints` -function update_dist!(b::LazyBridgeOptimizer, constraints) +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...) + 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 + +# 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) # Bellman-Ford algorithm - changed = true # Has b.dist changed in the last iteration ? + changed = true # Has b.constraint_dist changed in the last iteration ? while changed changed = false - for BT in b.bridgetypes + for BT in b.variable_bridge_types + for (S,) in variables + if Variable.supports_constrained_variable(BT, S) && + supports_added_no_update(b, BT, S) + # Number of bridges needed using BT + dist = 1 + added_dist(b, BT, S) + # Is it better that what can currently be done ? + if dist < _dist(b, S) + b.variable_dist[(S,)] = dist + b.variable_best[(S,)] = Variable.concrete_bridge_type(BT, S) + changed = true + end + end + end + end + for BT in b.constraint_bridge_types for (F, S) in constraints - if MOI.supports_constraint(BT, F, S) && all(C -> supports_constraint_no_update(b, C[1], C[2]), added_constraint_types(BT, F, S)) + if MOI.supports_constraint(BT, F, S) && + supports_added_no_update(b, BT, F, S) # Number of bridges needed using BT - dist = 1 + mapreduce( - C -> _dist(b, C[1], C[2]), +, - added_constraint_types(BT, F, S), init = 0) + dist = 1 + added_dist(b, BT, F, S) # Is it better that what can currently be done ? if dist < _dist(b, F, S) - b.dist[(F, S)] = dist - b.best[(F, S)] = Constraint.concrete_bridge_type(BT, F, S) + b.constraint_dist[(F, S)] = dist + b.constraint_best[(F, S)] = Constraint.concrete_bridge_type(BT, F, S) changed = true end end @@ -66,59 +123,172 @@ function update_dist!(b::LazyBridgeOptimizer, constraints) end end -function fill_required_constraints!(required::Set{Tuple{DataType, DataType}}, b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) - if supports_constraint_no_update(b, F, S) +function fill_required!(required_variables::Set{Tuple{DataType}}, + required_constraints::Set{Tuple{DataType, DataType}}, + b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) + if supports_no_update(b, S) + return # The constraint is supported + end + if (S,) in required_variables + return # The requirements for these constrained variables have already been added or are being added + end + # The constrained variables are not supported yet, add in + # `required_variables` the required variables types to bridge it. + 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 + end + end +end + +function fill_required!(required_variables::Set{Tuple{DataType}}, + required_constraints::Set{Tuple{DataType, DataType}}, + b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) + if supports_no_update(b, F, S) return # The constraint is supported end - if (F, S) in required + if (F, S) in required_constraints return # The requirements for this constraint have already been added or are being added end - # The constraint is not supported yet, add in `required` the required constraint types to bridge it - push!(required, (F, S)) - for BT in b.bridgetypes + # The constraint is not supported yet, add + # * in `required_variables` the required constrained variables types and + # * in `required_constraints` the required constraint types + # to bridge it. + 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_constraints!(required, b, C[1], C[2]) + fill_required!(required_variables, required_constraints, b, C[1], C[2]) end end end end -# Compute dist[(F, S)] and best[(F, S)] -function update_constraint!(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) - required = Set{Tuple{DataType, DataType}}() - fill_required_constraints!(required, b, F, S) - update_dist!(b, required) +# Compute dist[(F, S)], dist[(S,)] and best[(F, S)], best[(S,)] +function update!(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) +end + +""" + add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.AbstractBridge}) + +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)) end """ add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Constraint.AbstractBridge}) -Enable the use of the bridges of type `BT` by `b`. +Enable the use of the constraint bridges of type `BT` by `b`. """ function add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Constraint.AbstractBridge}) - push!(b.bridgetypes, BT) - # Some constraints (F, S) in keys(b.best) may now be bridged - # with a less briges than `b.dist[(F, S)] using `BT` - update_dist!(b, keys(b.best)) + 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)) +end + +function _remove_bridge(bridge_types::Vector, BT::Type) + i = findfirst(isequal(BT), bridge_types) + if i === nothing + error("Cannot remove bridge `BT` as it was never added or was already", + " removed.") + else + deleteat!(bridge_types, i) + end + return +end +function _reset_dist(b::LazyBridgeOptimizer) + empty!(b.variable_dist) + empty!(b.variable_best) + empty!(b.constraint_dist) + empty!(b.constraint_best) +end + +""" + remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.AbstractBridge}) + +Disable the use of the variable bridges of type `BT` by `b`. +""" +function remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.AbstractBridge}) + _remove_bridge(b.variable_bridge_types, BT) + _reset_dist(b) end +""" + remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:Constraint.AbstractBridge}) + +Disable the use of the constraint bridges of type `BT` by `b`. +""" +function remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:Constraint.AbstractBridge}) + _remove_bridge(b.constraint_bridge_types, BT) + _reset_dist(b) +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) +end function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) - !MOI.supports_constraint(b.model, F, S) + return !MOI.supports_constraint(b.model, F, S) end -# Same as supports_constraint but do not trigger `update_constraint!`. This is -# used inside `update_constraint!`. -function supports_constraint_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.best) +# 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) end -function supports_bridging_constraint(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) - update_constraint!(b, F, S) - return (F, S) in keys(b.best) +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 + +supports_constraint_bridges(::LazyBridgeOptimizer) = true +function supports_bridging_constrained_variable( + b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet} +) + update!(b, (S,)) + return (S,) in keys(b.variable_best) +end +function supports_bridging_constraint( + b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet} +) + 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 + update!(b, (S,)) + result = get(b.variable_best, (S,), nothing) + if result === nothing + throw(MOI.UnsupportedConstraint{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 - update_constraint!(b, F, S) - result = get(b.best, (F, S), nothing) + update!(b, (F, S)) + result = get(b.constraint_best, (F, S), nothing) if result === nothing throw(MOI.UnsupportedConstraint{F, S}()) end diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index e2c80c70a9..e5b669cd82 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -78,7 +78,8 @@ function MOI.supports_constraint( end struct BridgeAddingNoConstraint{T} <: MOI.Bridges.Constraint.AbstractBridge end -MOIB.added_constraint_types(::Type{BridgeAddingNoConstraint{T}}) where {T} = [] +MOIB.added_constrained_variable_types(::Type{<:BridgeAddingNoConstraint}) = Tuple{DataType}[] +MOIB.added_constraint_types(::Type{<:BridgeAddingNoConstraint}) = Tuple{DataType, DataType}[] function MOI.supports_constraint(::Type{<:BridgeAddingNoConstraint}, ::Type{MOI.SingleVariable}, ::Type{MOI.Integer}) @@ -175,8 +176,8 @@ end @test MOIB.bridge_type(bridged_mock, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) == MOIB.Constraint.RSOCtoPSDBridge{Float64, MOI.VectorOfVariables} @test MOIB.bridge(bridged_mock, c) isa MOIB.Constraint.RSOCtoPSDBridge - @test bridged_mock.dist[(MOI.VectorOfVariables, - MOI.RotatedSecondOrderCone)] == 1 + @test bridged_mock.constraint_dist[(MOI.VectorOfVariables, + MOI.RotatedSecondOrderCone)] == 1 end @testset "Supports" begin diff --git a/test/Bridges/utilities.jl b/test/Bridges/utilities.jl index d8b591dab0..a1ea48ac8a 100644 --- a/test/Bridges/utilities.jl +++ b/test/Bridges/utilities.jl @@ -9,8 +9,7 @@ function test_delete_bridge( m::MOIB.AbstractBridgeOptimizer, ci::MOI.ConstraintIndex{F, S}, nvars::Int, nocs::Tuple; used_bridges = 1, num_bridged = 1) where {F, S} function num_bridges() - return count(bridge -> bridge !== nothing, m.bridges) + - length(m.single_variable_constraints) + return count(bridge -> true, values(MOIB.Constraint.bridges(m))) end start_num_bridges = num_bridges() @test MOI.get(m, MOI.NumberOfVariables()) == nvars From 291c11b4321d43e2f4153fcc171ebcb9d9af2e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 Aug 2019 08:49:18 +0200 Subject: [PATCH 2/2] Address comments --- src/Bridges/Variable/single_bridge_optimizer.jl | 4 ++-- src/Bridges/bridge_optimizer.jl | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Bridges/Variable/single_bridge_optimizer.jl b/src/Bridges/Variable/single_bridge_optimizer.jl index 669eaecc5a..035236753a 100644 --- a/src/Bridges/Variable/single_bridge_optimizer.jl +++ b/src/Bridges/Variable/single_bridge_optimizer.jl @@ -8,8 +8,8 @@ even if they are supported by one of its bridges. !!! note Two bridge optimizers using variable bridges cannot be used together as both - of them assume that the underlying model only return variable indices with - nonnegative index. + of them assume that the underlying model only returns variable indices with + nonnegative values. """ mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer model::OT diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 874ce35889..493314b3f3 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -98,7 +98,7 @@ function bridge_type end Returns whether `ci` is the constraint of a bridged constrained variable. That is, if it was returned by `Variable.add_key_for_bridge` or -`Variable.add_keys_for_bridge`. Note that it is note equivalent to +`Variable.add_keys_for_bridge`. Note that it is not equivalent to `ci.value < 0` as, it can also simply be a constraint on a bridged variable. """ is_variable_bridged(::AbstractBridgeOptimizer, ::MOI.ConstraintIndex) = false @@ -232,7 +232,7 @@ function MOI.delete(b::AbstractBridgeOptimizer, vis::Vector{MOI.VariableIndex}) end function MOI.delete(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) if Constraint.has_bridges(Constraint.bridges(b)) - # Delete all `MOI.SingleVariable` constraint of this variable + # Delete all `MOI.SingleVariable` constraints of this variable for ci in Constraint.variable_constraints(Constraint.bridges(b), vi) MOI.delete(b, ci) end @@ -295,6 +295,12 @@ starts with `value = MOI.get(b.model, args...)`, otherwise, starts with * if `F`-in-`S` constraints may correspond to bridged constraints, modify it with `operate_constraint_bridges!`; then return the final `value`. + +For instance, [`MOI.supports`](@ref) calls this function with +`init = () -> true`, `operate_variable_bridges(ok) = +ok && MOI.supports(b, attr, Variable.concrete_bridge_type(b, S))` and +`operate_constraint_bridges(ok) = ok && +MOI.supports(b, attr, Constraint.concrete_bridge_type(b, F, S))`. """ function reduce_bridged( b::AbstractBridgeOptimizer, @@ -402,7 +408,7 @@ function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraints) set_of_types = Constraint.list_of_key_types(Constraint.bridges(b)) union!(set_of_types, Variable.list_of_constraint_types(Variable.bridges(b))) # There may be types already in `list_of_types` of a supported constraint - # was force-bridged because a variable in the `SingleVariable` or + # that was force-bridged because a variable in the `SingleVariable` or # `VectorOfVariables` function was bridged even though the constraint type # is supported by `b.model`. As `set_of_types` is a set, these duplicates # are merge automatically.