From 5dceb9bf5bddb8e49ba580ec1bcf31e2e4528164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 1 Jul 2020 14:13:26 +0200 Subject: [PATCH] Implement bridge cost --- src/Bridges/Constraint/map.jl | 103 ++++------ .../Constraint/single_bridge_optimizer.jl | 13 +- .../Variable/single_bridge_optimizer.jl | 1 + src/Bridges/bridge_optimizer.jl | 137 ++++++++++--- src/Bridges/debug.jl | 53 ++++-- src/Bridges/graph.jl | 6 + src/Bridges/lazy_bridge_optimizer.jl | 54 ++---- src/Utilities/copy.jl | 180 +++++++----------- test/Bridges/Constraint/map.jl | 2 +- test/Bridges/Variable/zeros.jl | 13 -- test/Bridges/bridge_optimizer.jl | 2 + test/Bridges/lazy_bridge_optimizer.jl | 64 ++++--- test/Utilities/copy.jl | 73 ++++++- 13 files changed, 390 insertions(+), 311 deletions(-) diff --git a/src/Bridges/Constraint/map.jl b/src/Bridges/Constraint/map.jl index 646366c0ba..436fdfa772 100644 --- a/src/Bridges/Constraint/map.jl +++ b/src/Bridges/Constraint/map.jl @@ -14,13 +14,10 @@ struct Map <: AbstractDict{MOI.ConstraintIndex, AbstractBridge} # of creation so we need `OrderedDict` and not `Dict`. # For `SingleVariable` constraints: (variable, set type) -> bridge single_variable_constraints::OrderedDict{Tuple{Int64, DataType}, AbstractBridge} - # For `VectorVariable` constraints: (variable, set type) -> bridge - vector_of_variables_constraints::OrderedDict{Tuple{Int64, DataType}, AbstractBridge} end function Map() return Map(Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[], - OrderedDict{Tuple{Int64, DataType}, AbstractBridge}(), OrderedDict{Tuple{Int64, DataType}, AbstractBridge}()) end @@ -28,29 +25,27 @@ end function Base.isempty(map::Map) return all(bridge -> bridge === nothing, map.bridges) && - isempty(map.single_variable_constraints) && - isempty(map.vector_of_variables_constraints) + isempty(map.single_variable_constraints) end function Base.empty!(map::Map) empty!(map.bridges) empty!(map.constraint_types) empty!(map.single_variable_constraints) - empty!(map.vector_of_variables_constraints) return map end function Base.length(map::Map) return count(bridge -> bridge !== nothing, map.bridges) + - length(map.single_variable_constraints) + - length(map.vector_of_variables_constraints) + length(map.single_variable_constraints) end +_index(ci::MOI.ConstraintIndex) = ci.value +_index(ci::MOI.ConstraintIndex{MOI.VectorOfVariables}) = -ci.value function Base.haskey(map::Map, ci::MOI.ConstraintIndex{F, S}) where {F, S} - return 1 ≤ ci.value ≤ length(map.bridges) && - map.bridges[ci.value] !== nothing && - (F, S) == map.constraint_types[ci.value] + return 1 ≤ _index(ci) ≤ length(map.bridges) && + map.bridges[_index(ci)] !== nothing && + (F, S) == map.constraint_types[_index(ci)] end -function Base.getindex(map::Map, - ci::MOI.ConstraintIndex) - return map.bridges[ci.value] +function Base.getindex(map::Map, ci::MOI.ConstraintIndex) + return map.bridges[_index(ci)] end function Base.haskey(map::Map, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S return haskey(map.single_variable_constraints, (ci.value, S)) @@ -59,49 +54,27 @@ function Base.getindex(map::Map, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S return map.single_variable_constraints[(ci.value, S)] end -function Base.haskey(map::Map, ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S - return haskey(map.vector_of_variables_constraints, (ci.value, S)) -end -function Base.getindex(map::Map, - ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S - return map.vector_of_variables_constraints[(ci.value, S)] -end function Base.delete!(map::Map, ci::MOI.ConstraintIndex) - map.bridges[ci.value] = nothing + map.bridges[_index(ci)] = nothing return map end function Base.delete!(map::Map, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S delete!(map.single_variable_constraints, (ci.value, S)) return map end -function Base.delete!(map::Map, ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S - delete!(map.vector_of_variables_constraints, (ci.value, S)) - return map -end function Base.values(map::Map) return Base.Iterators.flatten(( # See comment in `values(::Variable.Map)`. Base.Iterators.Filter(bridge -> bridge !== nothing, map.bridges), - values(map.single_variable_constraints), - values(map.vector_of_variables_constraints) + values(map.single_variable_constraints) )) end -# Implementation of iterate: it should combine non-variablewise constraints, -# `SingleVariable` constraints and `VectorOfVariables` constraints. -function _iterate_vov(map::Map, elem_state=iterate(map.vector_of_variables_constraints)) - if elem_state === nothing - return nothing - else - i, S = elem_state[1].first - bridge = elem_state[1].second - ci = MOI.ConstraintIndex{MOI.VectorOfVariables, S}(i) - return ci => bridge, (3, elem_state[2]) - end -end +# Implementation of iterate: it should combine non-`SingleVariable` constraints and +# `SingleVariable` constraints. function _iterate_sv(map::Map, elem_state=iterate(map.single_variable_constraints)) if elem_state === nothing - return _iterate_vov(map) + return nothing else i, S = elem_state[1].first bridge = elem_state[1].second @@ -109,6 +82,12 @@ function _iterate_sv(map::Map, elem_state=iterate(map.single_variable_constraint return ci => bridge, (2, elem_state[2]) end end +function _index(index, F, S) + return MOI.ConstraintIndex{F, S}(index) +end +function _index(index, F::Type{MOI.VectorOfVariables}, S) + return MOI.ConstraintIndex{F, S}(-index) +end function _iterate(map::Map, state=1) while state ≤ length(map.bridges) && map.bridges[state] === nothing state += 1 @@ -117,17 +96,15 @@ function _iterate(map::Map, state=1) return _iterate_sv(map) else F, S = map.constraint_types[state] - return MOI.ConstraintIndex{F, S}(state) => map.bridges[state], (1, state + 1) + return _index(state, F, S) => map.bridges[state], (1, state + 1) end end Base.iterate(map::Map) = _iterate(map) function Base.iterate(map::Map, state) if state[1] == 1 return _iterate(map, state[2]) - elseif state[1] == 2 - return _iterate_sv(map, iterate(map.single_variable_constraints, state[2])) else - return _iterate_vov(map, iterate(map.vector_of_variables_constraints, state[2])) + return _iterate_sv(map, iterate(map.single_variable_constraints, state[2])) end end @@ -149,14 +126,14 @@ were created with `add_key_for_bridge`. """ function keys_of_type end -function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{F, S}}) where {F, S} - return count(i -> haskey(map, C(i)), eachindex(map.bridges)) +function number_of_type(map::Map, ::Type{MOI.ConstraintIndex{F, S}}) where {F, S} + return count(i -> haskey(map, _index(i, F, S)), eachindex(map.bridges)) end function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{F, S}}) where {F, S} return Base.Iterators.Filter( ci -> haskey(map, ci), MOIU.LazyMap{C}( - i -> C(i), eachindex(map.bridges))) + i -> _index(i, F, S), eachindex(map.bridges))) end function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, S}}) where S return count(key -> key[2] == S, keys(map.single_variable_constraints)) @@ -167,15 +144,6 @@ function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, Base.Iterators.Filter(key -> key[2] == S, keys(map.single_variable_constraints)) ) end -function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariables, S}}) where S - return count(key -> key[2] == S, keys(map.vector_of_variables_constraints)) -end -function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariables, S}}) where S - return MOIU.LazyMap{C}( - key -> C(key[1]), - Base.Iterators.Filter(key -> key[2] == S, keys(map.vector_of_variables_constraints)) - ) -end """ list_of_key_types(map::Map) @@ -192,9 +160,6 @@ function list_of_key_types(map::Map) for key in keys(map.single_variable_constraints) push!(list, (MOI.SingleVariable, key[2])) end - for key in keys(map.vector_of_variables_constraints) - push!(list, (MOI.VectorOfVariables, key[2])) - end return list end @@ -222,8 +187,11 @@ Return the list of all keys that correspond to """ function vector_of_variables_constraints(map::Map) return MOIU.LazyMap{MOI.ConstraintIndex{MOI.VectorOfVariables}}( - key -> MOI.ConstraintIndex{MOI.VectorOfVariables, key[2]}(key[1]), - keys(map.vector_of_variables_constraints) + i -> MOI.ConstraintIndex{map.constraint_types[i]...}(-i), + Base.Iterators.Filter( + i -> map.bridges[i] !== nothing && map.constraint_types[i][1] == MOI.VectorOfVariables, + eachindex(map.bridges) + ) ) end @@ -238,8 +206,7 @@ operations in case variable bridges are not used. """ function has_bridges(map::Map) return !isempty(map.bridges) || - !isempty(map.single_variable_constraints) || - !isempty(map.vector_of_variables_constraints) + !isempty(map.single_variable_constraints) end @@ -256,19 +223,13 @@ function add_key_for_bridge(map::Map, bridge::AbstractBridge, func::MOI.AbstractFunction, set::MOI.AbstractSet) push!(map.bridges, bridge) push!(map.constraint_types, (typeof(func), typeof(set))) - return MOI.ConstraintIndex{typeof(func), typeof(set)}(length(map.bridges)) + return _index(length(map.bridges), typeof(func), typeof(set)) end function add_key_for_bridge(map::Map, bridge::AbstractBridge, func::MOI.SingleVariable, set::MOI.AbstractScalarSet) map.single_variable_constraints[(func.variable.value, typeof(set))] = bridge return MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}(func.variable.value) end -function add_key_for_bridge(map::Map, bridge::AbstractBridge, - func::MOI.VectorOfVariables, set::MOI.AbstractVectorSet) - index = first(func.variables).value - map.vector_of_variables_constraints[(index, typeof(set))] = bridge - return MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}(index) -end """ EmptyMap <: AbstractDict{MOI.ConstraintIndex, AbstractBridge} diff --git a/src/Bridges/Constraint/single_bridge_optimizer.jl b/src/Bridges/Constraint/single_bridge_optimizer.jl index 5fe1700407..a6fc6903dc 100644 --- a/src/Bridges/Constraint/single_bridge_optimizer.jl +++ b/src/Bridges/Constraint/single_bridge_optimizer.jl @@ -25,8 +25,16 @@ function bridges(bridge::SingleBridgeOptimizer) end MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = true -function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet}) - return false +# If `BT` bridges `MOI.Reals` (such as `Constraint.FunctionizeBridge` bridge, +# without this method, it creates a `StackOverflow` with +# `is_bridged`, `supports_bridging_constrained_variable` +# and `supports_add_constrained_variables`. +MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{MOI.Reals}) = false +function MOIB.is_bridged(b::SingleBridgeOptimizer, S::Type{<:MOI.AbstractSet}) + return MOIB.supports_bridging_constrained_variable(b, S) +end +function MOIB.supports_bridging_constrained_variable(b::SingleBridgeOptimizer, S::Type{<:MOI.AbstractSet}) + return MOIB.supports_bridging_constraint(b, MOIU.variable_function_type(S), S) && MOI.supports_add_constrained_variables(b, MOI.Reals) end function MOIB.supports_bridging_constraint( ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, @@ -45,3 +53,4 @@ function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, ::Type{<:MOI.AbstractSet}) where BT return BT end +MOIB.bridging_cost(::SingleBridgeOptimizer, args...) = 1.0 diff --git a/src/Bridges/Variable/single_bridge_optimizer.jl b/src/Bridges/Variable/single_bridge_optimizer.jl index 7490090fc7..ce2e4d5c3b 100644 --- a/src/Bridges/Variable/single_bridge_optimizer.jl +++ b/src/Bridges/Variable/single_bridge_optimizer.jl @@ -55,3 +55,4 @@ function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, ::Type{<:MOI.AbstractSet}) where BT return BT end +MOIB.bridging_cost(::SingleBridgeOptimizer, args...) = 1.0 diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index b5103828c6..31f47b6c22 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -55,10 +55,35 @@ function is_bridged(b::AbstractBridgeOptimizer, return is_bridged(b, F, S) end 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 + ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S + # There are a few cases for which we should return `false`: + # 1) It was added as variables constrained on creation to `b.model`, + # In this case, `is_bridged(b, S)` is `false` and `ci.value >= 0`. + # 2) It was added as constraint on a non-bridged variable to `b.model`, + # In this case, `is_bridged(b, F, S)` is `false` and `ci.value >= 0`. + # and a few cases for which we should return `true`: + # 3) It was added with a variable bridge, + # In this case, `is_bridged(b, S)` is `true` and `ci.value < 0`. + # 4) It was added as constraint on a bridged variable so it was force-bridged, + # In this case, `ci.value < 0`. + # 5) It was added with a constraint bridge, + # In this case, `is_bridged(b, F, S)` is `true` and `ci.value >= 0` (the variable is non-bridged, otherwise, the constraint would have been force-bridged). + # So + # * if, `ci.value < 0` then it is case 3) or 4) and we return `true`. + # * Otherwise, + # - if `is_bridged(b, S)` and `is_bridged(b, F, S)` then 1) and 2) are + # not possible so we are in case 5) and we return `true`. + # - if `!is_bridged(b, F, S)`, then 5) is not possible and we return `false`. + # - if `!is_bridged(b, S)` and `is_bridged(b, F, S)`, then it is either case 1) + # or 5). They cannot both be the cases as one cannot add two `SingleVariable` + # with the same set type on the same variable (this is ensured by + # `_check_double_single_variable`). Therefore, we can safely determine + # whether it is bridged with `haskey(Constraint.bridges(b), ci)`. + return ci.value < 0 || (is_bridged(b, MOI.SingleVariable, S) && (is_bridged(b, S) || haskey(Constraint.bridges(b), ci))) +end +function is_bridged(b::AbstractBridgeOptimizer, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S + return ci.value < 0 end """ @@ -431,13 +456,16 @@ function reduce_bridged( init, model_value, operate_variable_bridges!, operate_constraint_bridges!) - if is_bridged(b, F, S) - value = init() - else + variable_function = F == MOIU.variable_function_type(S) + # A `F`-in-`S` could be added to the model either if it this constraint + # is not bridged or if variables constrained on creations to `S` are not bridged + # and `F` is `SingleVariable` or `VectorOfVariables`. + if !is_bridged(b, F, S) || (variable_function && !is_bridged(b, S)) value = model_value() + else + value = init() end - variable_function = F == MOIU.variable_function_type(S) - if variable_function && is_bridged(b, S) + if variable_function && is_bridged(b, S) && is_variable_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 @@ -593,6 +621,34 @@ function MOI.set(b::AbstractBridgeOptimizer, return MOI.set(b.model, attr, bridged_function(b, value)) end +""" + bridging_cost(b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractSet}}) + +Return the cost of bridging variables constrained in `S` on creation, +`is_bridged(b, S)` is assumed to be `true`. + + bridging_cost(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction, S::Type{<:MOI.AbstractSet}}) + +Return the cost of bridging `F`-in-`S` constraints, +`is_bridged(b, S)` is assumed to be `true`. +""" +function bridging_cost end + +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.VariableBridgingCost{S}) where S + if is_bridged(b, S) + return bridging_cost(b, S) + else + return MOI.get(b.model, attr) + end +end +function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintBridgingCost{F, S}) where {F, S} + if is_bridged(b, F, S) + return bridging_cost(b, F, S) + else + return MOI.get(b.model, attr) + end +end + # Objective """ @@ -993,10 +1049,6 @@ function MOI.supports_constraint(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) if is_bridged(b, F, S) - if F == MOIU.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) @@ -1008,6 +1060,20 @@ function add_bridged_constraint(b, BridgeType, f, s) Variable.register_context(Variable.bridges(b), ci) return ci end +function _check_double_single_variable(b::AbstractBridgeOptimizer, func::MOI.SingleVariable, set) + if !is_bridged(b, typeof(set)) + # The variable might have been constrained in `b.model` to a set of type + # `typeof(set)` on creation. + ci = MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}(func.variable.value) + if MOI.is_valid(b.model, ci) + error("The variable `$(func.variable)` was constrained on creation ", + "to a set of type `$(typeof(set))`. Therefore, a ", + "`SingleVariable`-in-`$(typeof(set))` cannot be added on this ", + "variable. Use `MOI.set` with `MOI.ConstraintSet()` instead.") + end + end +end +function _check_double_single_variable(b::AbstractBridgeOptimizer, func, set) end function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, s::MOI.AbstractSet) if Variable.has_bridges(Variable.bridges(b)) @@ -1024,19 +1090,6 @@ function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, 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_vector_functionize_bridge(b), typeof(f), typeof(s)) return add_bridged_constraint(b, BridgeType, f, s) @@ -1046,6 +1099,7 @@ function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, end end if is_bridged(b, typeof(f), typeof(s)) + _check_double_single_variable(b, f, s) # We compute `BridgeType` first as `concrete_bridge_type` calls # `bridge_type` which might throw an `UnsupportedConstraint` error in # which case, we do not want any modification to have been done @@ -1151,10 +1205,26 @@ function MOI.add_variables(b::AbstractBridgeOptimizer, n) end end +# Split in two to avoid ambiguity +function MOI.supports_add_constrained_variables( + b::AbstractBridgeOptimizer, ::Type{MOI.Reals}) + if is_bridged(b, MOI.Reals) + return supports_bridging_constrained_variable(b, MOI.Reals) + else + return MOI.supports_add_constrained_variables(b.model, MOI.Reals) + end +end +function MOI.supports_add_constrained_variables( + b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractVectorSet}) + if is_bridged(b, S) + return supports_bridging_constrained_variable(b, S) + else + return MOI.supports_add_constrained_variables(b.model, S) + 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 is_bridged(b, typeof(set)) if set isa MOI.Reals || is_variable_bridged(b, typeof(set)) BridgeType = Variable.concrete_bridge_type(b, typeof(set)) return Variable.add_keys_for_bridge(Variable.bridges(b), @@ -1169,10 +1239,17 @@ function MOI.add_constrained_variables(b::AbstractBridgeOptimizer, return MOI.add_constrained_variables(b.model, set) end end +function MOI.supports_add_constrained_variable( + b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractScalarSet}) + if is_bridged(b, S) + return supports_bridging_constrained_variable(b, S) + else + return MOI.supports_add_constrained_variable(b.model, S) + 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 is_bridged(b, typeof(set)) if is_variable_bridged(b, typeof(set)) BridgeType = Variable.concrete_bridge_type(b, typeof(set)) return Variable.add_key_for_bridge(Variable.bridges(b), diff --git a/src/Bridges/debug.jl b/src/Bridges/debug.jl index 68da5d4beb..c17e3f09c1 100644 --- a/src/Bridges/debug.jl +++ b/src/Bridges/debug.jl @@ -182,29 +182,44 @@ function debug_unsupported(io::IO, b::LazyBridgeOptimizer, node::AbstractNode) add_unsupported(b.graph, node, variables, constraints, objectives) print_unsupported(io, b, _sort(variables), _sort(constraints), _sort(objectives)) end + +const UNSUPPORTED_MESSAGE = " are not supported and cannot be bridged into supported" * + " constrained variables and constraints. See details below:" + +function debug(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}; io::IO = Base.stdout) + if (S <: MOI.AbstractScalarSet && MOI.supports_add_constrained_variable(b, S)) || + (S <: MOI.AbstractVectorSet && MOI.supports_add_constrained_variables(b, S)) + MOIU.print_with_acronym(io, "Constrained variables in `$S` are supported.\n") + else + MOIU.print_with_acronym(io, "Constrained variables in `$S`") + println(io, UNSUPPORTED_MESSAGE) + debug_unsupported(io, b, node(b, S)) + end +end + +""" + debug_supports_add_constrained_variable( + b::LazyBridgeOptimizer, + S::Type{<:MOI.AbstractSet}; + io::IO = Base.stdout + ) + +Prints to `io` explanations for the value of +[`MOI.supports_add_constrained_variable`](@ref) with the same arguments. +""" +function debug_supports_add_constrained_variable( + b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}; kws...) + debug(b, S; kws...) +end + function debug(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}; io::IO = Base.stdout) if MOI.supports_constraint(b, F, S) - if F == MOIU.variable_function_type(S) - # This may be thanks to a variable bridge so `F`-in-`S` constraints - # are maybe not supported but constrained variables in `S` are - # definitely supported. - MOIU.print_with_acronym(io, "Constrained variables in `$S` are supported.\n") - else - MOIU.print_with_acronym(io, "`$F`-in-`$S` constraints are supported.\n") - end + MOIU.print_with_acronym(io, "`$F`-in-`$S` constraints are supported.\n") else - message = " are not supported and cannot be bridged into supported" * - " constrained variables and constraints. See details below:" - if F == MOIU.variable_function_type(S) - MOIU.print_with_acronym(io, "Constrained variables in `$S`") - println(io, message) - debug_unsupported(io, b, node(b, S)) - else - MOIU.print_with_acronym(io, "`$F`-in-`$S` constraints") - println(io, message) - debug_unsupported(io, b, node(b, F, S)) - end + MOIU.print_with_acronym(io, "`$F`-in-`$S` constraints") + println(io, UNSUPPORTED_MESSAGE) + debug_unsupported(io, b, node(b, F, S)) end end diff --git a/src/Bridges/graph.jl b/src/Bridges/graph.jl index ef54818a9a..3125f0878d 100644 --- a/src/Bridges/graph.jl +++ b/src/Bridges/graph.jl @@ -139,6 +139,12 @@ function add_objective_node(graph::Graph) return ObjectiveNode(length(graph.objective_best)) end +function bridging_cost(graph::Graph, node::AbstractNode) + bellman_ford!(graph) + dist = _dist(graph, node) + return dist == INFINITY ? Inf : float(dist) +end + function bridge_index(graph::Graph, node::VariableNode) bellman_ford!(graph) return graph.variable_best[node.index] diff --git a/src/Bridges/lazy_bridge_optimizer.jl b/src/Bridges/lazy_bridge_optimizer.jl index f07cbf4d46..bdab990238 100644 --- a/src/Bridges/lazy_bridge_optimizer.jl +++ b/src/Bridges/lazy_bridge_optimizer.jl @@ -29,7 +29,6 @@ mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer # Bellman-Ford graph graph::Graph # List of types of available bridges - variable_free_bridge_type::Union{Nothing, DataType} variable_bridge_types::Vector{Any} variable_node::OrderedDict{Tuple{DataType}, VariableNode} variable_types::Vector{Tuple{DataType}} @@ -52,7 +51,7 @@ function LazyBridgeOptimizer(model::MOI.ModelLike) Constraint.Map(), Dict{MOI.ConstraintIndex, String}(), nothing, Objective.Map(), Graph(), - nothing, Any[], OrderedDict{Tuple{DataType}, VariableNode}(), Tuple{DataType}[], + Any[], OrderedDict{Tuple{DataType}, VariableNode}(), Tuple{DataType}[], Any[], OrderedDict{Tuple{DataType, DataType}, ConstraintNode}(), Tuple{DataType, DataType}[], Any[], OrderedDict{Tuple{DataType}, ObjectiveNode}(), Tuple{DataType}[], Dict{Any, DataType}(), @@ -119,9 +118,8 @@ end function node(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) F = MOIU.variable_function_type(S) - if (MOI.supports_constraint(b.model, F, S) - || (S <: MOI.AbstractScalarSet && MOI.supports_add_constrained_variable(b.model, S)) - || (S <: MOI.AbstractVectorSet && MOI.supports_add_constrained_variables(b.model, S))) + if (S <: MOI.AbstractScalarSet && MOI.supports_add_constrained_variable(b.model, S)) || + (S <: MOI.AbstractVectorSet && MOI.supports_add_constrained_variables(b.model, S)) return VariableNode(0) end variable_node = get(b.variable_node, (S,), nothing) @@ -220,13 +218,6 @@ end function _add_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge}) push!(_bridge_types(b, BT), BT) end -function _add_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.FreeBridge}) - if b.variable_free_bridge_type !== nothing - error("Bridge $BT cannot be added as $(b.variable_free_bridge_type) has already been added. Remove it first with `M̀OI.Bridges.remove_bridge`.") - else - b.variable_free_bridge_type = BT - end -end """ remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge}) @@ -249,14 +240,6 @@ function _remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge}) deleteat!(bridge_types, i) return true end -function _remove_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.FreeBridge}) - if b.variable_free_bridge_type === nothing || b.variable_free_bridge_type != BT - return false - end - b.variable_free_bridge_type = nothing - return true -end - """ has_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge}) @@ -266,13 +249,13 @@ Return a `Bool` indicating whether the bridges of type `BT` are used by `b`. function has_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge}) return findfirst(isequal(BT), _bridge_types(b, BT)) !== nothing end -function has_bridge(b::LazyBridgeOptimizer, BT::Type{<:Variable.FreeBridge}) - return b.variable_free_bridge_type == BT -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, MOIU.variable_function_type(S), S) +function is_bridged(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractScalarSet}) + return !MOI.supports_add_constrained_variable(b.model, S) +end +function is_bridged(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractVectorSet}) + return !MOI.supports_add_constrained_variables(b.model, S) end function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) return !MOI.supports_constraint(b.model, F, S) @@ -295,15 +278,14 @@ function bridge_index(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}) end supports_constraint_bridges(::LazyBridgeOptimizer) = true -function supports_bridging_constrained_variable( - b::LazyBridgeOptimizer, ::Type{MOI.Reals} -) - return b.variable_free_bridge_type !== nothing -end function supports_bridging_constrained_variable( b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet} ) - return !iszero(bridge_index(b, S)) + variable_node = node(b, S) + constraint_node = b.graph.variable_constraint_node[variable_node.index] + return !iszero(bridge_index(b.graph, variable_node)) || + (constraint_node.index != INVALID_NODE_INDEX && + !iszero(bridge_index(b.graph, constraint_node))) end function supports_bridging_constraint( b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, @@ -317,13 +299,7 @@ function supports_bridging_objective_function( return !iszero(bridge_index(b, F)) end function is_variable_bridged(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) - return is_variable_edge_best(b.graph, node(b, S)) -end -function bridge_type(b::LazyBridgeOptimizer, ::Type{MOI.Reals}) - if b.variable_free_bridge_type === nothing - throw(MOI.UnsupportedConstraint{MOI.VectorOfVariables, MOI.Reals}()) - end - return b.variable_free_bridge_type + return is_bridged(b, S) && is_variable_edge_best(b.graph, node(b, S)) end function bridge_type(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet}) @@ -407,3 +383,5 @@ end function objective_functionize_bridge(b::LazyBridgeOptimizer) return _functionize_bridge(b.objective_bridge_types, Objective.FunctionizeBridge) end + +bridging_cost(b::LazyBridgeOptimizer, args...) = bridging_cost(b.graph, node(b, args...)) diff --git a/src/Utilities/copy.jl b/src/Utilities/copy.jl index 28458e4859..0ed5ec4367 100644 --- a/src/Utilities/copy.jl +++ b/src/Utilities/copy.jl @@ -196,27 +196,31 @@ end Copy the constraints of type `MOI.VectorOfVariables`-in-`S` from the model `src` to the model `dest` and fill `idxmap` accordingly. The copy is only done when the variables to be copied are not already keys of `idxmap`. It returns a list -of the constraints not copied. +of the constraints copied and not copied. """ -function copy_vector_of_variables(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, - S::Type{<:MOI.AbstractSet}) +function copy_vector_of_variables( + dest::MOI.ModelLike, src::MOI.ModelLike, idxmap::IndexMap, + S::Type{<:MOI.AbstractSet}, + copy_constrained_variables::Function=MOI.add_constrained_variables +) + added = MOI.ConstraintIndex{MOI.VectorOfVariables, S}[] not_added = MOI.ConstraintIndex{MOI.VectorOfVariables, S}[] cis_src = MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}()) fs_src = MOI.get(src, MOI.ConstraintFunction(), cis_src)::Vector{MOI.VectorOfVariables} for (ci_src, f_src) in zip(cis_src, fs_src) if all(vi -> !haskey(idxmap, vi), f_src.variables) && allunique(f_src.variables) set = MOI.get(src, MOI.ConstraintSet(), ci_src)::S - vis_dest, ci_dest = MOI.add_constrained_variables(dest, set) + vis_dest, ci_dest = copy_constrained_variables(dest, set) idxmap[ci_src] = ci_dest for (vi_src, vi_dest) in zip(f_src.variables, vis_dest) idxmap[vi_src] = vi_dest end + push!(added, ci_src) else push!(not_added, ci_src) end end - return not_added + return added, not_added end """ @@ -229,23 +233,27 @@ the model `dest` and fill `idxmap` accordingly. The copy is only done when the variables to be copied are not already keys of `idxmap`. It returns a list of the constraints not copied. """ -function copy_single_variable(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, - S::Type{<:MOI.AbstractSet}) +function copy_single_variable( + dest::MOI.ModelLike, src::MOI.ModelLike, idxmap::IndexMap, + S::Type{<:MOI.AbstractSet}, + copy_constrained_variable::Function=MOI.add_constrained_variable +) + added = MOI.ConstraintIndex{MOI.SingleVariable, S}[] not_added = MOI.ConstraintIndex{MOI.SingleVariable, S}[] cis_src = MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable, S}()) fs_src = MOI.get(src, MOI.ConstraintFunction(), cis_src)::Vector{MOI.SingleVariable} for (ci_src, f_src) in zip(cis_src, fs_src) if !haskey(idxmap, f_src.variable) set = MOI.get(src, MOI.ConstraintSet(), ci_src)::S - vi_dest, ci_dest = MOI.add_constrained_variable(dest, set) + vi_dest, ci_dest = copy_constrained_variable(dest, set) idxmap[ci_src] = ci_dest idxmap[f_src.variable] = vi_dest + push!(added, ci_src) else push!(not_added, ci_src) end end - return not_added + return added, not_added end """ @@ -325,6 +333,50 @@ function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike) default_copy_to(dest, src, true) end +function sorted_variable_sets_by_cost(dest::MOI.ModelLike, src::MOI.ModelLike) + constraint_types = MOI.get(src, MOI.ListOfConstraints()) + single_or_vector_variables_types = [ + (F, S) for (F, S) in constraint_types + if F === MOI.SingleVariable || F === MOI.VectorOfVariables + ] + sort!(single_or_vector_variables_types; by=((F, S),) -> + # We give priority for sets such that there is a big cost reduction + # constraining the variable on creation. + (MOI.get(dest, MOI.VariableBridgingCost{S}()) - MOI.get(dest, MOI.ConstraintBridgingCost{F, S}()), + # In case of ties, we give priority to vector sets. + # See issue #987 + F === MOI.SingleVariable) + ) + return single_or_vector_variables_types +end + +function try_constrain_variables_on_creation( + dest::MOI.ModelLike, src::MOI.ModelLike, idxmap, + copy_constrained_variables::Function, copy_constrained_variable::Function +) + single_or_vector_variables_types = sorted_variable_sets_by_cost(dest, src) + vector_of_variables_types = Type{<:MOI.AbstractVectorSet}[] + vector_of_variables_added = Vector{<:MOI.ConstraintIndex{MOI.VectorOfVariables}}[] + vector_of_variables_not_added = Vector{<:MOI.ConstraintIndex{MOI.VectorOfVariables}}[] + single_variable_types = Type{<:MOI.AbstractScalarSet}[] + single_variable_added = Vector{<:MOI.ConstraintIndex{MOI.SingleVariable}}[] + single_variable_not_added = Vector{<:MOI.ConstraintIndex{MOI.SingleVariable}}[] + for (F, S) in single_or_vector_variables_types + if F === MOI.VectorOfVariables + added, not_added = copy_vector_of_variables(dest, src, idxmap, S, copy_constrained_variables) + push!(vector_of_variables_added, added) + push!(vector_of_variables_not_added, not_added) + push!(vector_of_variables_types, S) + elseif F === MOI.SingleVariable + added, not_added = copy_single_variable(dest, src, idxmap, S, copy_constrained_variable) + push!(single_variable_added, added) + push!(single_variable_not_added, not_added) + push!(single_variable_types, S) + end + end + return vector_of_variables_types, vector_of_variables_added, vector_of_variables_not_added, single_variable_types, single_variable_added, single_variable_not_added +end + """ default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool) @@ -338,12 +390,10 @@ function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bo vis_src = MOI.get(src, MOI.ListOfVariableIndices()) idxmap = index_map_for_variable_indices(vis_src) - constraint_types = MOI.get(src, MOI.ListOfConstraints()) - single_variable_types = Type{<:MOI.AbstractScalarSet}[] - vector_of_variables_types = Type{<:MOI.AbstractVectorSet}[] # The `NLPBlock` assumes that the order of variables does not change (#849) if MOI.NLPBlock() in MOI.get(src, MOI.ListOfModelAttributesSet()) + constraint_types = MOI.get(src, MOI.ListOfConstraints()) single_variable_types = [S for (F, S) in constraint_types if F == MOI.SingleVariable] vector_of_variables_types = [S for (F, S) in constraint_types @@ -357,23 +407,10 @@ function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bo for S in single_variable_types ] else - # Order the copying of the variables by 1) their variable bridging cost 2) by starting with the vectors, as what was done before - # See issue #987 - single_or_vector_variables_types = [(F, S) for (F, S) in constraint_types - if F == MOI.SingleVariable || F == MOI.VectorOfVariables] - sorted_by_cost = sortperm(single_or_vector_variables_types; by=((F, S),) -> (MOI.get(dest, MOI.VariableBridgingCost{S}()) - MOI.get(dest, MOI.ConstraintBridgingCost{F, S}()), F == MOI.SingleVariable)) - vector_of_variables_not_added = Vector{Array{MOI.ConstraintIndex{MOI.VectorOfVariables, <:MOI.AbstractVectorSet}}}() - single_variable_not_added = Vector{Array{MOI.ConstraintIndex{MOI.SingleVariable, <:MOI.AbstractScalarSet}}}() - for i in sorted_by_cost - F, S = single_or_vector_variables_types[i] - if F == MOI.VectorOfVariables - push!(vector_of_variables_not_added, copy_vector_of_variables(dest, src, idxmap, S)) - push!(vector_of_variables_types, S) - elseif F == MOI.SingleVariable - push!(single_variable_not_added, copy_single_variable(dest, src, idxmap, S)) - push!(single_variable_types, S) - end - end + vector_of_variables_types, _, vector_of_variables_not_added, + single_variable_types, _, single_variable_not_added = try_constrain_variables_on_creation( + dest, src, idxmap, MOI.add_constrained_variables, MOI.add_constrained_variable + ) end copy_free_variables(dest, idxmap, vis_src, MOI.add_variables) @@ -552,67 +589,6 @@ function load(model::MOI.ModelLike, attr::Union{MOI.AbstractVariableAttribute, M end end -""" - allocate_single_variable(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, S::Type{<:MOI.AbstractSet}) - -Allocate the constraints of type `MOI.SingleVariable`-in-`S` from the model -`src` to the model `dest` and fill `idxmap` accordingly. The copy is only done -when the variables to be copied are not already keys of `idxmap`. It returns a -list of the constraints allocated and those not allocated. -""" -function allocate_single_variable(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, S::Type{<:MOI.AbstractSet}) - allocated = MOI.ConstraintIndex{MOI.SingleVariable, S}[] - not_allocated = MOI.ConstraintIndex{MOI.SingleVariable, S}[] - cis_src = MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable, S}()) - fs_src = MOI.get(src, MOI.ConstraintFunction(), cis_src)::Vector{MOI.SingleVariable} - for (ci_src, f_src) in zip(cis_src, fs_src) - if !haskey(idxmap, f_src.variable) - set = MOI.get(src, MOI.ConstraintSet(), ci_src)::S - vi_dest, ci_dest = allocate_constrained_variable(dest, set) - idxmap[ci_src] = ci_dest - idxmap[f_src.variable] = vi_dest - push!(allocated, ci_src) - else - push!(not_allocated, ci_src) - end - end - return allocated, not_allocated -end - -""" - allocate_vector_of_variables(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, S::Type{<:MOI.AbstractSet}) - -Allocate the constraints of type `MOI.VectorOfVariables`-in-`S` from the model -`src` to the model `dest` and fill `idxmap` accordingly. The copy is only done -when the variables to be copied are not already keys of `idxmap`. It returns a -list of the constraints allocated and a list of those not allocated. -""" -function allocate_vector_of_variables(dest::MOI.ModelLike, src::MOI.ModelLike, - idxmap::IndexMap, - S::Type{<:MOI.AbstractSet}) - allocated = MOI.ConstraintIndex{MOI.VectorOfVariables, S}[] - not_allocated = MOI.ConstraintIndex{MOI.VectorOfVariables, S}[] - cis_src = MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}()) - fs_src = MOI.get(src, MOI.ConstraintFunction(), cis_src)::Vector{MOI.VectorOfVariables} - for (ci_src, f_src) in zip(cis_src, fs_src) - if all(vi -> !haskey(idxmap, vi), f_src.variables) && allunique(f_src.variables) - set = MOI.get(src, MOI.ConstraintSet(), ci_src)::S - vis_dest, ci_dest = allocate_constrained_variables(dest, set) - idxmap[ci_src] = ci_dest - for (vi_src, vi_dest) in zip(f_src.variables, vis_dest) - idxmap[vi_src] = vi_dest - end - push!(allocated, ci_src) - else - push!(not_allocated, ci_src) - end - end - return allocated, not_allocated -end - """ load_single_variable( dest::MOI.ModelLike, src::MOI.ModelLike, idxmap::IndexMap, @@ -700,24 +676,14 @@ function allocate_load(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool idxmap = index_map_for_variable_indices(vis_src) constraint_types = MOI.get(src, MOI.ListOfConstraints()) single_variable_types = [S for (F, S) in constraint_types - if F == MOI.SingleVariable] + if F === MOI.SingleVariable] vector_of_variables_types = [S for (F, S) in constraint_types - if F == MOI.VectorOfVariables] + if F === MOI.VectorOfVariables] # Allocate variables - vector_of_variables_allocated = Vector{Vector{<:MOI.ConstraintIndex{MOI.VectorOfVariables}}}(undef, length(vector_of_variables_types)) - vector_of_variables_not_allocated = Vector{Vector{<:MOI.ConstraintIndex{MOI.VectorOfVariables}}}(undef, length(vector_of_variables_types)) - for (i, S) in enumerate(vector_of_variables_types) - vector_of_variables_allocated[i], vector_of_variables_not_allocated[i] = - allocate_vector_of_variables(dest, src, idxmap, S) - end - - single_variable_allocated = Vector{Vector{<:MOI.ConstraintIndex{MOI.SingleVariable}}}(undef, length(single_variable_types)) - single_variable_not_allocated = Vector{Vector{<:MOI.ConstraintIndex{MOI.SingleVariable}}}(undef, length(single_variable_types)) - for (i, S) in enumerate(single_variable_types) - single_variable_allocated[i], single_variable_not_allocated[i] = - allocate_single_variable(dest, src, idxmap, S) - end + vector_of_variables_types, vector_of_variables_allocated, vector_of_variables_not_allocated, single_variable_type, single_variable_allocated, single_variable_not_allocated = try_constrain_variables_on_creation( + dest, src, idxmap, allocate_constrained_variables, allocate_constrained_variable + ) copy_free_variables(dest, idxmap, vis_src, allocate_variables) diff --git a/test/Bridges/Constraint/map.jl b/test/Bridges/Constraint/map.jl index 86e459c4b4..2203412da2 100644 --- a/test/Bridges/Constraint/map.jl +++ b/test/Bridges/Constraint/map.jl @@ -33,7 +33,7 @@ b2 = ConstraintDummyBridge(2) vov = MOI.VectorOfVariables([x, y]) c2 = MOIB.Constraint.add_key_for_bridge(map, b2, vov, MOI.SecondOrderCone(2)) @testset "VectorOfVariables" begin - @test c2.value == x.value + @test c2.value == -1 @test haskey(map, c2) @test map[c2] == b2 @test MOIB.Constraint.number_of_type(map, typeof(c2)) == 1 diff --git a/test/Bridges/Variable/zeros.jl b/test/Bridges/Variable/zeros.jl index 0cf7fbbf85..5b676169d4 100644 --- a/test/Bridges/Variable/zeros.jl +++ b/test/Bridges/Variable/zeros.jl @@ -63,19 +63,6 @@ err = ErrorException( ) @test_throws err MOI.delete(bridged_mock, cyz) -err = ErrorException( - "Cannot add two `VectorOfVariables`-in-`MathOptInterface.Zeros` on the" * - " same first variable MathOptInterface.VariableIndex(-1)." -) -@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables(yz), MOI.Zeros(2)) - -err = ErrorException( - "Cannot `VectorOfVariables`-in-`MathOptInterface.Zeros` for" * - " which some variables are bridged but not the first one" * - " `MathOptInterface.VariableIndex(12345679)`." -) -@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables([x, y]), MOI.Zeros(2)) - err = ErrorException( "Cannot unbridge function because some variables are bridged by" * " variable bridges that do not support reverse mapping, e.g.," * diff --git a/test/Bridges/bridge_optimizer.jl b/test/Bridges/bridge_optimizer.jl index 42da6e72f4..75dcca6eb6 100644 --- a/test/Bridges/bridge_optimizer.jl +++ b/test/Bridges/bridge_optimizer.jl @@ -148,6 +148,8 @@ bridged_mock = MOIB.Constraint.LessToGreater{Float64}(MOIB.Constraint.SplitInter x = MOI.add_variable(bridged_mock) ci = MOI.add_constraint(bridged_mock, MOI.SingleVariable(x), MOI.Interval(0.0, 1.0)) + @test !MOI.Bridges.is_bridged(bridged_mock, ci) + @test MOI.Bridges.is_bridged(bridged_mock.model, ci) @test !MOI.supports(bridged_mock, attr, typeof(ci)) @test_throws err MOI.get(bridged_mock, attr, ci) end diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index fece135cf1..52f120fbc6 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -32,11 +32,6 @@ const MOIB = MathOptInterface.Bridges err = ErrorException("Cannot remove bridge `$BT` as it was never added or was already removed.") @test_throws err MOIB.remove_bridge(bridged, BT) end - BT1 = MOIB.Variable.FreeBridge{Float64} - MOIB.add_bridge(bridged, BT1) - BT2 = MOIB.Variable.FreeBridge{T} - err = ErrorException("Bridge $BT2 cannot be added as $BT1 has already been added. Remove it first with `M̀OI.Bridges.remove_bridge`.") - @test_throws err MOIB.add_bridge(bridged, BT2) end include("utilities.jl") @@ -220,7 +215,9 @@ MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.G MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.LessThan{T}}) where {T} = false MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.EqualTo{T}}) where {T} = false MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.Interval{T}}) where {T} = false -MOI.supports_constraint(::SDPAModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Reals}) = false +MOI.supports_add_constrained_variables(::SDPAModel, ::Type{MOI.Nonnegatives}) = true +MOI.supports_add_constrained_variables(::SDPAModel, ::Type{MOI.PositiveSemidefiniteConeTriangle}) = true +MOI.supports_add_constrained_variables(::SDPAModel, ::Type{MOI.Reals}) = false MOI.supports(::SDPAModel{T}, ::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}) where {T} = false MOI.supports(::SDPAModel, ::MOI.ObjectiveFunction{MOI.SingleVariable}) = false @@ -258,48 +255,63 @@ end @testset "Nonpositives" begin @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonpositives) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Nonpositives) + @test !MOI.supports_add_constrained_variables(bridged, MOI.Nonpositives) MOIB.add_bridge(bridged, MOIB.Variable.NonposToNonnegBridge{T}) - @test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Nonpositives) + @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Nonpositives) + @test MOI.supports_add_constrained_variables(bridged, MOI.Nonpositives) @test MOIB.bridge_type(bridged, MOI.Nonpositives) == MOIB.Variable.NonposToNonnegBridge{T} end @testset "Zeros" begin @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) + @test !MOI.supports_add_constrained_variables(bridged, MOI.Zeros) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Zeros) MOIB.add_bridge(bridged, MOIB.Variable.ZerosBridge{T}) - @test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Zeros) + @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Zeros) + @test MOI.supports_add_constrained_variables(bridged, MOI.Zeros) @test MOIB.bridge_type(bridged, MOI.Zeros) == MOIB.Variable.ZerosBridge{T} end @testset "Free" begin @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Reals) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals) + @test !MOI.supports_add_constrained_variables(bridged, 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 !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals) + @test MOI.supports_add_constrained_variables(bridged, MOI.Reals) @test MOIB.bridge_type(bridged, MOI.Reals) == MOIB.Variable.FreeBridge{T} end @testset "Vectorize" begin @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T}) @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.GreaterThan{T}) + @test !MOI.supports_add_constrained_variable(bridged, MOI.GreaterThan{T}) @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{T}) @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.LessThan{T}) + @test !MOI.supports_add_constrained_variable(bridged, MOI.LessThan{T}) @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{T}) @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.EqualTo{T}) + @test !MOI.supports_add_constrained_variable(bridged, MOI.EqualTo{T}) MOIB.add_bridge(bridged, MOIB.Variable.VectorizeBridge{T}) - @test MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.GreaterThan{T}) + @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.GreaterThan{T}) + @test MOI.supports_add_constrained_variable(bridged, MOI.GreaterThan{T}) @test MOIB.bridge_type(bridged, MOI.GreaterThan{T}) == MOIB.Variable.VectorizeBridge{T, MOI.Nonnegatives} - @test MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.LessThan{T}) + @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.LessThan{T}) + @test MOI.supports_add_constrained_variable(bridged, MOI.LessThan{T}) @test MOIB.bridge_type(bridged, MOI.LessThan{T}) == MOIB.Variable.VectorizeBridge{T, MOI.Nonpositives} - @test MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.EqualTo{T}) + @test !MOI.supports_constraint(bridged, MOI.SingleVariable, MOI.EqualTo{T}) + @test MOI.supports_add_constrained_variable(bridged, MOI.EqualTo{T}) @test MOIB.bridge_type(bridged, MOI.EqualTo{T}) == MOIB.Variable.VectorizeBridge{T, MOI.Zeros} end @testset "RSOCtoPSD" begin @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) + @test !MOI.supports_add_constrained_variables(bridged, MOI.RotatedSecondOrderCone) MOIB.add_bridge(bridged, MOIB.Variable.RSOCtoPSDBridge{T}) @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) + @test !MOI.supports_add_constrained_variables(bridged, MOI.RotatedSecondOrderCone) MOIB.add_bridge(bridged, MOIB.Constraint.ScalarFunctionizeBridge{T}) - @test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) + @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) + @test MOI.supports_add_constrained_variables(bridged, MOI.RotatedSecondOrderCone) @test MOIB.bridge_type(bridged, MOI.RotatedSecondOrderCone) == MOIB.Variable.RSOCtoPSDBridge{T} end @testset "Combining two briges" begin @@ -401,20 +413,18 @@ end return String(resize!(s.data, s.size)) end @testset "LessThan variables" begin - F = MOI.SingleVariable S = MOI.LessThan{T} - @test debug_string(MOIB.debug_supports_constraint, F, S) == """ + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == """ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below: [1] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. """ @test sprint(MOIB.print_graph, bridged) == """ -Bridge graph with 1 variable nodes, 1 constraint nodes and 0 objective nodes. +Bridge graph with 1 variable nodes, 0 constraint nodes and 0 objective nodes. [1] constrained variables in `MOI.LessThan{$T}` are not supported - (1) `MOI.SingleVariable`-in-`MOI.LessThan{$T}` constraints are not supported """ MOIB.add_bridge(bridged, MOIB.Variable.VectorizeBridge{T}) - @test debug_string(MOIB.debug_supports_constraint, F, S) == """ + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == """ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below: [1] constrained variables in `MOI.LessThan{$T}` are not supported because: Cannot use `MOIB.Variable.VectorizeBridge{$T,MOI.Nonpositives}` because: @@ -424,7 +434,7 @@ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be brid Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. """ MOIB.add_bridge(bridged, MOIB.Variable.NonposToNonnegBridge{T}) - @test debug_string(MOIB.debug_supports_constraint, F, S) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n" + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n" @test sprint(MOIB.print_graph, bridged) == """ Bridge graph with 2 variable nodes, 0 constraint nodes and 0 objective nodes. [1] constrained variables in `MOI.LessThan{$T}` are bridged (distance 2) by MOIB.Variable.VectorizeBridge{$T,MOI.Nonpositives}. @@ -434,9 +444,8 @@ Bridge graph with 2 variable nodes, 0 constraint nodes and 0 objective nodes. bridged = MOIB.LazyBridgeOptimizer(model) @testset "LessThan variables with ScalarFunctionizeBridge" begin MOIB.add_bridge(bridged, MOIB.Constraint.ScalarFunctionizeBridge{T}) - F = MOI.SingleVariable S = MOI.LessThan{T} - @test debug_string(MOIB.debug_supports_constraint, F, S) == """ + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == """ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below: [1] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because: @@ -444,13 +453,12 @@ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be brid (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it. """ @test sprint(MOIB.print_graph, bridged) == """ -Bridge graph with 1 variable nodes, 2 constraint nodes and 0 objective nodes. +Bridge graph with 1 variable nodes, 1 constraint nodes and 0 objective nodes. [1] constrained variables in `MOI.LessThan{$T}` are not supported (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported - (2) `MOI.SingleVariable`-in-`MOI.LessThan{$T}` constraints are not supported """ MOIB.add_bridge(bridged, MOIB.Variable.VectorizeBridge{T}) - @test debug_string(MOIB.debug_supports_constraint, F, S) == """ + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == """ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below: [1] constrained variables in `MOI.LessThan{$T}` are not supported because: Cannot use `MOIB.Variable.VectorizeBridge{$T,MOI.Nonpositives}` because: @@ -462,7 +470,7 @@ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be brid (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it. """ MOIB.add_bridge(bridged, MOIB.Variable.NonposToNonnegBridge{T}) - @test debug_string(MOIB.debug_supports_constraint, F, S) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n" + @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n" @test sprint(MOIB.print_graph, bridged) == """ Bridge graph with 2 variable nodes, 1 constraint nodes and 0 objective nodes. [1] constrained variables in `MOI.LessThan{$T}` are bridged (distance 2) by MOIB.Variable.VectorizeBridge{$T,MOI.Nonpositives}. @@ -748,9 +756,9 @@ end @test MOIB.bridge(bridged, cy) isa MOIB.Constraint.RSOCBridge{T} @test sprint(MOIB.print_graph, bridged) == """ Bridge graph with 4 variable nodes, 9 constraint nodes and 0 objective nodes. - [1] constrained variables in `MOI.SecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (1). - [2] constrained variables in `MOI.RotatedSecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (3). - [3] constrained variables in `MOI.PositiveSemidefiniteConeTriangle` are not supported + [1] constrained variables in `MOI.RotatedSecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (3). + [2] constrained variables in `MOI.PositiveSemidefiniteConeTriangle` are not supported + [3] constrained variables in `MOI.SecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (1). [4] constrained variables in `MOI.Nonnegatives` are supported (distance 2) by adding free variables and then constrain them, see (6). (1) `MOI.VectorOfVariables`-in-`MOI.SecondOrderCone` constraints are bridged (distance 1) by MOIB.Constraint.VectorFunctionizeBridge{$T,MOI.SecondOrderCone}. (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by MOIB.Constraint.RSOCBridge{$T,MOI.VectorAffineFunction{$T},MOI.VectorAffineFunction{$T}}. diff --git a/test/Utilities/copy.jl b/test/Utilities/copy.jl index 549668d9a8..6e44c2a33e 100644 --- a/test/Utilities/copy.jl +++ b/test/Utilities/copy.jl @@ -181,7 +181,7 @@ end MOI.add_variables(model::AbstractConstrainedVariablesModel, n) = MOI.add_variables(model.inner, n) MOI.add_variable(model::AbstractConstrainedVariablesModel) = MOI.add_variable(model.inner) -function MOI.add_constraint(model::AbstractConstrainedVariablesModel, f::F, s::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet} +function MOI.add_constraint(model::AbstractConstrainedVariablesModel, f::MOI.AbstractFunction, s::MOI.AbstractSet) ci = MOI.add_constraint(model.inner, f, s) push!(model.constraintIndices, ci) return ci @@ -209,6 +209,8 @@ MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = false MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.Nonpositives}) = true +MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.VectorAffineFunction{Float64}}, ::Type{MOI.Nonnegatives}) = true +MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorAffineFunction{Float64}}, ::Type{MOI.Nonpositives}) = true MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.GreaterThan}) = true MOI.supports_add_constrained_variable(::OrderConstrainedVariablesModel, ::Type{<:MOI.GreaterThan}) = false @@ -220,7 +222,6 @@ MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, : MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.LessThan}) = true MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, ::Type{<:MOI.LessThan}) = false - @testset "Create variables using supports_add_constrained_variable(s) (#987)" begin # With vectors src = MOIU.Model{Float64}() @@ -238,7 +239,55 @@ MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, : @test typeof(c1) == typeof(dest.constraintIndices[1]) @test typeof(c2) == typeof(dest.constraintIndices[2]) + b, cb = MOI.add_constrained_variables(src, MOI.Nonnegatives(2)) + c3 = MOI.add_constraint(src, b, MOI.Zeros(2)) + + d, cd = MOI.add_constrained_variables(src, MOI.Zeros(2)) + c4 = MOI.add_constraint(src, d, MOI.Nonpositives(2)) + dest = OrderConstrainedVariablesModel() + bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64) + @test MOIU.sorted_variable_sets_by_cost(bridged_dest, src) == [ + (MOI.VectorOfVariables, MOI.Zeros), + (MOI.VectorOfVariables, MOI.Nonnegatives), + (MOI.VectorOfVariables, MOI.Nonpositives) + ] + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Nonnegatives) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Nonnegatives}()) == 0.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Nonnegatives) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Nonnegatives}()) == 0.0 + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Nonpositives) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Nonpositives}()) == 1.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Nonpositives) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Nonpositives}()) == 1.0 + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Zeros) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Zeros}()) == 1.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Zeros) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Zeros}()) == 2.0 + index_map = MOI.copy_to(bridged_dest, src) + @test length(dest.constraintIndices) == 4 + + dest = ReverseOrderConstrainedVariablesModel() + bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64) + @test MOIU.sorted_variable_sets_by_cost(bridged_dest, src) == [ + (MOI.VectorOfVariables, MOI.Zeros), + (MOI.VectorOfVariables, MOI.Nonpositives), + (MOI.VectorOfVariables, MOI.Nonnegatives) + ] + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Nonnegatives) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Nonnegatives}()) == 2.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Nonnegatives) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Nonnegatives}()) == 1.0 + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Nonpositives) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Nonpositives}()) == 0.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Nonpositives) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Nonpositives}()) == 1.0 + @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Zeros) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Zeros}()) == 1.0 + @test MOI.supports_constraint(bridged_dest, MOI.VectorOfVariables, MOI.Zeros) + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables, MOI.Zeros}()) == 3.0 + index_map = MOI.copy_to(bridged_dest, src) + @test length(dest.constraintIndices) == 4 # With single variables src = MOIU.Model{Float64}() @@ -255,4 +304,24 @@ MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, : index_map = MOI.copy_to(dest, src) @test typeof(c1) == typeof(dest.constraintIndices[1]) @test typeof(c2) == typeof(dest.constraintIndices[2]) + + dest = OrderConstrainedVariablesModel() + bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.LessThan{Float64}}()) == 0.0 + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 2.0 + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}()) == 1.0 + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 0.0 + index_map = MOI.copy_to(bridged_dest, src) + @test typeof(c1) == typeof(dest.constraintIndices[2]) + @test typeof(c2) == typeof(dest.constraintIndices[1]) + + dest = ReverseOrderConstrainedVariablesModel() + bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64) + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.LessThan{Float64}}()) == 1.0 + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 0.0 + @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}()) == 0.0 + @test MOI.get(bridged_dest, MOI.ConstraintBridgingCost{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 2.0 + index_map = MOI.copy_to(bridged_dest, src) + @test typeof(c1) == typeof(dest.constraintIndices[1]) + @test typeof(c2) == typeof(dest.constraintIndices[2]) end