From f8d5c6311756bba4b732bc9d8d1ed0c19b48e229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 27 Oct 2019 13:36:33 +0100 Subject: [PATCH 1/6] Refactor constraint bridges with AbstractSetMapBridge --- src/Bridges/Constraint/Constraint.jl | 67 +------- src/Bridges/Constraint/flip_sign.jl | 154 +++--------------- src/Bridges/Constraint/function_conversion.jl | 59 +++++++ src/Bridges/Constraint/functionize.jl | 4 +- src/Bridges/Constraint/norm_to_lp.jl | 90 +++++----- src/Bridges/Constraint/rsoc.jl | 123 ++++++-------- src/Bridges/Constraint/set_map.jl | 99 +++++++++++ src/Bridges/Constraint/socr.jl | 72 -------- src/Bridges/bridge.jl | 16 ++ src/Utilities/functions.jl | 9 + src/sets.jl | 10 ++ test/Bridges/Constraint/bridge.jl | 20 +++ test/Bridges/Constraint/flip_sign.jl | 52 ++++-- test/Bridges/Constraint/functionize.jl | 2 + test/Bridges/Constraint/norm_to_lp.jl | 10 +- test/Bridges/Constraint/rsoc.jl | 45 ++++- test/Bridges/Constraint/socr.jl | 39 ----- 17 files changed, 432 insertions(+), 439 deletions(-) create mode 100644 src/Bridges/Constraint/function_conversion.jl create mode 100644 src/Bridges/Constraint/set_map.jl delete mode 100644 src/Bridges/Constraint/socr.jl create mode 100644 test/Bridges/Constraint/bridge.jl delete mode 100644 test/Bridges/Constraint/socr.jl diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 0996fcf2ef..945902c18d 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -26,67 +26,10 @@ end # Constraint bridges # Function conversion bridges +include("function_conversion.jl") +# Transformation between a set S1 and a set S2 such that A*S1 = S2 for some linear map A +include("set_map.jl") -""" - abstract type AbstractFunctionConversionBridge <: AbstractBridge end - -Bridge a constraint `F`-in-`S` into a constraint `G`-in-`S` where `F` and `G` -are equivalent representations of the same function. By convention, the -transformed function is stored in the `constraint` field. -""" -abstract type AbstractFunctionConversionBridge <: AbstractBridge end - -function MOI.get(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, - bridge::AbstractFunctionConversionBridge) - if invariant_under_function_conversion(attr) - return MOI.get(model, attr, bridge.constraint) - else - throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support accessing the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) - end -end - -function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, - bridge::AbstractFunctionConversionBridge, value) - if invariant_under_function_conversion(attr) - return MOI.set(model, attr, bridge.constraint, value) - else - throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support setting the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) - end -end - -""" - invariant_under_function_conversion(attr::MOI.AbstractConstraintAttribute) - -Returns whether the value of the attribute does not change if the constraint -`F`-in-`S` is transformed into a constraint `G`-in-`S` where `F` and `G` are -equivalent representations of the same function. If it returns true, then -subtypes of [`Constraint.AbstractFunctionConversionBridge`](@ref) such as -[`Constraint.ScalarFunctionizeBridge`](@ref) and -[`Constraint.VectorFunctionizeBridge`](@ref) will automatically support -[`MOI.get`](@ref) and [`MOI.set`](@ref) for `attr`. -""" -invariant_under_function_conversion(::MOI.AbstractConstraintAttribute) = false - -function invariant_under_function_conversion(::Union{ - MOI.ConstraintSet, - MOI.ConstraintBasisStatus, - MOI.ConstraintPrimal, - MOI.ConstraintPrimalStart, - MOI.ConstraintDual, - MOI.ConstraintDualStart}) - return true -end - -include("functionize.jl") -const ScalarFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarFunctionizeBridge{T}, OT} -const VectorFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorFunctionizeBridge{T}, OT} -# TODO add affine -> quadratic conversion bridge - -include("flip_sign.jl") -const GreaterToLess{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GreaterToLessBridge{T}, OT} -const LessToGreater{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LessToGreaterBridge{T}, OT} -const NonnegToNonpos{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonnegToNonposBridge{T}, OT} -const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT} include("vectorize.jl") const Vectorize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorizeBridge{T}, OT} include("scalarize.jl") @@ -96,10 +39,6 @@ const ScalarSlack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarSlackBridg const VectorSlack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorSlackBridge{T}, OT} include("interval.jl") const SplitInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SplitIntervalBridge{T}, OT} -include("rsoc.jl") -const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT} -include("socr.jl") -const SOCR{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCRBridge{T}, OT} include("quad_to_soc.jl") const QuadtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T}, OT} include("norm_to_lp.jl") diff --git a/src/Bridges/Constraint/flip_sign.jl b/src/Bridges/Constraint/flip_sign.jl index 6a223313cf..eb07d292d6 100644 --- a/src/Bridges/Constraint/flip_sign.jl +++ b/src/Bridges/Constraint/flip_sign.jl @@ -3,86 +3,45 @@ Bridge a `G`-in-`S1` constraint into an `F`-in-`S2` constraint by multiplying the function by `-1` and taking the point reflection of the set across the -origin. The flipped `F`-in-`S` constraint is stored in the `flipped_constraint` +origin. The flipped `F`-in-`S` constraint is stored in the `constraint` field by convention. """ abstract type FlipSignBridge{ T, S1<:MOI.AbstractSet, S2<:MOI.AbstractSet, - F<:MOI.AbstractFunction, G<:MOI.AbstractFunction} <: AbstractBridge end + F<:MOI.AbstractFunction, G<:MOI.AbstractFunction} <: SetMapBridge{T, S2, S1, F, G} end -function MOI.supports_constraint( - ::Type{<:FlipSignBridge{T, S1}}, ::Type{<:MOI.AbstractScalarFunction}, - ::Type{S1}) where {T, S1<:MOI.AbstractScalarSet} - return true -end -function MOI.supports_constraint( - ::Type{<:FlipSignBridge{T, S1}}, ::Type{<:MOI.AbstractVectorFunction}, - ::Type{S1}) where {T, S1<:MOI.AbstractVectorSet} - return true -end -MOIB.added_constrained_variable_types(::Type{<:FlipSignBridge}) = Tuple{DataType}[] -function MOIB.added_constraint_types( - ::Type{<:FlipSignBridge{T, S1, S2, F}}) where {T, S1, S2, F} - return [(F, S2)] -end +map_function(::Type{<:FlipSignBridge{T}}, func) where {T} = MOIU.operate(-, T, func) +# The map is an involution +inverse_map_function(BT::Type{<:FlipSignBridge}, func) = map_function(BT, func) +# The map is symmetric +adjoint_map_function(BT::Type{<:FlipSignBridge}, func) = map_function(BT, func) +# The map is a symmetric involution +inverse_adjoint_map_function(BT::Type{<:FlipSignBridge}, func) = map_function(BT, func) -# Attributes, Bridge acting as a model -function MOI.get(::FlipSignBridge{T, S1, S2, F}, - ::MOI.NumberOfConstraints{F, S2}) where {T, S1, S2, F} - return 1 -end -function MOI.get(bridge::FlipSignBridge{T, S1, S2, F}, - ::MOI.ListOfConstraintIndices{F, S2}) where {T, S1, S2, F} - return [bridge.flipped_constraint] -end - -# References -function MOI.delete(model::MOI.ModelLike, bridge::FlipSignBridge) - MOI.delete(model, bridge.flipped_constraint) -end function MOI.delete(model::MOI.ModelLike, bridge::FlipSignBridge, i::IndexInVector) - func = MOI.get(model, MOI.ConstraintFunction(), bridge.flipped_constraint) + func = MOI.get(model, MOI.ConstraintFunction(), bridge.constraint) idx = setdiff(1:MOI.output_dimension(func), i.value) new_func = MOIU.eachscalar(func)[idx] - set = MOI.get(model, MOI.ConstraintSet(), bridge.flipped_constraint) + set = MOI.get(model, MOI.ConstraintSet(), bridge.constraint) new_set = MOI.update_dimension(set, MOI.dimension(set) - 1) - MOI.delete(model, bridge.flipped_constraint) - bridge.flipped_constraint = MOI.add_constraint(model, new_func, new_set) -end - -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, - attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, - bridge::FlipSignBridge) - return -MOI.get(model, attr, bridge.flipped_constraint) -end - -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, - bridge::FlipSignBridge{T, S1, S2, F, G}) where {T, S1, S2, F, G} - func = MOIU.operate(-, T, MOI.get(model, attr, bridge.flipped_constraint)) - return MOIU.convert_approx(G, func) + MOI.delete(model, bridge.constraint) + bridge.constraint = MOI.add_constraint(model, new_func, new_set) end function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, change::MOI.ScalarCoefficientChange) MOI.modify( - model, bridge.flipped_constraint, + model, bridge.constraint, MOI.ScalarCoefficientChange(change.variable, -change.new_coefficient)) end - function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, change::MOI.MultirowChange{T}) where T new_coefficients = Tuple{Int64, T}[ (index, -coef) for (index, coef) in change.new_coefficients] - MOI.modify(model, bridge.flipped_constraint, + MOI.modify(model, bridge.constraint, MOI.MultirowChange(change.variable, new_coefficients)) end -function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, - change::MOI.VectorConstantChange) - MOI.modify(model, bridge.flipped_constraint, - MOI.VectorConstantChange(-change.new_constant)) -end """ GreaterToLessBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: @@ -93,16 +52,10 @@ constraint. """ struct GreaterToLessBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: FlipSignBridge{T, MOI.GreaterThan{T}, MOI.LessThan{T}, F, G} - flipped_constraint::CI{F, MOI.LessThan{T}} -end -function bridge_constraint(::Type{GreaterToLessBridge{T, F, G}}, - model::MOI.ModelLike, - g::MOI.AbstractScalarFunction, - s::MOI.GreaterThan) where {T, F, G} - f = MOIU.operate(-, T, g) - flipped_constraint = MOI.add_constraint(model, f, MOI.LessThan(-s.lower)) - return GreaterToLessBridge{T, F, G}(flipped_constraint) + constraint::CI{F, MOI.LessThan{T}} end +map_set(::Type{<:GreaterToLessBridge}, set::MOI.GreaterThan) = MOI.LessThan(-set.lower) +inverse_map_set(::Type{<:GreaterToLessBridge}, set::MOI.LessThan) = MOI.GreaterThan(-set.upper) function concrete_bridge_type(::Type{<:GreaterToLessBridge{T}}, G::Type{<:MOI.AbstractScalarFunction}, ::Type{MOI.GreaterThan{T}}) where T @@ -110,17 +63,6 @@ function concrete_bridge_type(::Type{<:GreaterToLessBridge{T}}, return GreaterToLessBridge{T, F, G} end -function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::GreaterToLessBridge, new_set::MOI.GreaterThan) - MOI.set(model, attr, bridge.flipped_constraint, - MOI.LessThan(-new_set.lower)) -end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::GreaterToLessBridge) - set = MOI.get(model, attr, bridge.flipped_constraint) - return MOI.GreaterThan(-set.upper) -end - """ LessToGreaterBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: FlipSignBridge{T, MOI.LessThan{T}, MOI.GreaterThan{T}, F, G} @@ -130,16 +72,10 @@ constraint. """ struct LessToGreaterBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: FlipSignBridge{T, MOI.LessThan{T}, MOI.GreaterThan{T}, F, G} - flipped_constraint::CI{F, MOI.GreaterThan{T}} -end -function bridge_constraint(::Type{LessToGreaterBridge{T, F, G}}, - model::MOI.ModelLike, - g::MOI.AbstractScalarFunction, - s::MOI.LessThan) where {T, F, G} - f = MOIU.operate(-, T, g) - flipped_constraint = MOI.add_constraint(model, f, MOI.GreaterThan(-s.upper)) - return LessToGreaterBridge{T, F, G}(flipped_constraint) + constraint::CI{F, MOI.GreaterThan{T}} end +map_set(::Type{<:LessToGreaterBridge}, set::MOI.LessThan) = MOI.GreaterThan(-set.upper) +inverse_map_set(::Type{<:LessToGreaterBridge}, set::MOI.GreaterThan) = MOI.LessThan(-set.lower) function concrete_bridge_type(::Type{<:LessToGreaterBridge{T}}, G::Type{<:MOI.AbstractScalarFunction}, ::Type{MOI.LessThan{T}}) where T @@ -147,16 +83,6 @@ function concrete_bridge_type(::Type{<:LessToGreaterBridge{T}}, return LessToGreaterBridge{T, F, G} end -function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::LessToGreaterBridge, new_set::MOI.LessThan) - MOI.set(model, attr, bridge.flipped_constraint, - MOI.GreaterThan(-new_set.upper)) -end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::LessToGreaterBridge) - set = MOI.get(model, attr, bridge.flipped_constraint) - return MOI.LessThan(-set.lower) -end """ NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction, G<:MOI.AbstractVectorFunction} <: @@ -167,17 +93,10 @@ constraint. """ mutable struct NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction, G<:MOI.AbstractVectorFunction} <: FlipSignBridge{T, MOI.Nonnegatives, MOI.Nonpositives, F, G} - flipped_constraint::CI{F, MOI.Nonpositives} -end -function bridge_constraint(::Type{NonnegToNonposBridge{T, F, G}}, - model::MOI.ModelLike, - g::MOI.AbstractVectorFunction, - s::MOI.Nonnegatives) where {T, F, G} - f = MOIU.operate(-, T, g) - flipped_constraint = MOI.add_constraint(model, f, - MOI.Nonpositives(s.dimension)) - return NonnegToNonposBridge{T, F, G}(flipped_constraint) + constraint::CI{F, MOI.Nonpositives} end +map_set(::Type{<:NonnegToNonposBridge}, set::MOI.Nonnegatives) = MOI.Nonpositives(set.dimension) +inverse_map_set(::Type{<:NonnegToNonposBridge}, set::MOI.Nonpositives) = MOI.Nonnegatives(set.dimension) function concrete_bridge_type(::Type{<:NonnegToNonposBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.Nonnegatives}) where T @@ -185,12 +104,6 @@ function concrete_bridge_type(::Type{<:NonnegToNonposBridge{T}}, return NonnegToNonposBridge{T, F, G} end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::NonnegToNonposBridge) - set = MOI.get(model, attr, bridge.flipped_constraint) - return MOI.Nonnegatives(MOI.dimension(set)) -end - """ NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction, G<:MOI.AbstractVectorFunction} <: FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives, F, G} @@ -200,26 +113,13 @@ constraint. """ mutable struct NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction, G<:MOI.AbstractVectorFunction} <: FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives, F, G} - flipped_constraint::CI{F, MOI.Nonnegatives} -end -function bridge_constraint(::Type{NonposToNonnegBridge{T, F, G}}, - model::MOI.ModelLike, - g::MOI.AbstractVectorFunction, - s::MOI.Nonpositives) where {T, F, G} - f = MOIU.operate(-, T, g) - flipped_constraint = MOI.add_constraint(model, f, - MOI.Nonnegatives(s.dimension)) - return NonposToNonnegBridge{T, F, G}(flipped_constraint) + constraint::CI{F, MOI.Nonnegatives} end +map_set(::Type{<:NonposToNonnegBridge}, set::MOI.Nonpositives) = MOI.Nonnegatives(set.dimension) +inverse_map_set(::Type{<:NonposToNonnegBridge}, set::MOI.Nonnegatives) = MOI.Nonpositives(set.dimension) function concrete_bridge_type(::Type{<:NonposToNonnegBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.Nonpositives}) where T F = MOIU.promote_operation(-, T, G) return NonposToNonnegBridge{T, F, G} end - -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, - bridge::NonposToNonnegBridge) - set = MOI.get(model, attr, bridge.flipped_constraint) - return MOI.Nonpositives(MOI.dimension(set)) -end diff --git a/src/Bridges/Constraint/function_conversion.jl b/src/Bridges/Constraint/function_conversion.jl new file mode 100644 index 0000000000..8a4094c4d5 --- /dev/null +++ b/src/Bridges/Constraint/function_conversion.jl @@ -0,0 +1,59 @@ +""" + abstract type AbstractFunctionConversionBridge{F, S} <: AbstractBridge end + +Bridge a constraint `G`-in-`S` into a constraint `F`-in-`S` where `F` and `G` +are equivalent representations of the same function. By convention, the +transformed function is stored in the `constraint` field. +""" +abstract type AbstractFunctionConversionBridge{F, S} <: AbstractBridge end + +function MOI.get(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, + bridge::AbstractFunctionConversionBridge) + if invariant_under_function_conversion(attr) + return MOI.get(model, attr, bridge.constraint) + else + throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support accessing the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) + end +end + +function MOI.supports(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, + ::Type{<:AbstractFunctionConversionBridge{F, S}}) where {F, S} + return invariant_under_function_conversion(attr) && + MOI.supports(model, attr, MOI.ConstraintIndex{F, S}) +end +function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, + bridge::AbstractFunctionConversionBridge, value) + if invariant_under_function_conversion(attr) + return MOI.set(model, attr, bridge.constraint, value) + else + throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support setting the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) + end +end + +""" + invariant_under_function_conversion(attr::MOI.AbstractConstraintAttribute) + +Returns whether the value of the attribute does not change if the constraint +`F`-in-`S` is transformed into a constraint `G`-in-`S` where `F` and `G` are +equivalent representations of the same function. If it returns true, then +subtypes of [`Constraint.AbstractFunctionConversionBridge`](@ref) such as +[`Constraint.ScalarFunctionizeBridge`](@ref) and +[`Constraint.VectorFunctionizeBridge`](@ref) will automatically support +[`MOI.get`](@ref) and [`MOI.set`](@ref) for `attr`. +""" +invariant_under_function_conversion(::MOI.AbstractConstraintAttribute) = false + +function invariant_under_function_conversion(::Union{ + MOI.ConstraintSet, + MOI.ConstraintBasisStatus, + MOI.ConstraintPrimal, + MOI.ConstraintPrimalStart, + MOI.ConstraintDual, + MOI.ConstraintDualStart}) + return true +end + +include("functionize.jl") +const ScalarFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarFunctionizeBridge{T}, OT} +const VectorFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorFunctionizeBridge{T}, OT} +# TODO add affine -> quadratic conversion bridge diff --git a/src/Bridges/Constraint/functionize.jl b/src/Bridges/Constraint/functionize.jl index 7c2ec93d34..cc078b9987 100644 --- a/src/Bridges/Constraint/functionize.jl +++ b/src/Bridges/Constraint/functionize.jl @@ -6,7 +6,7 @@ The `ScalarFunctionizeBridge` converts a constraint `SingleVariable`-in-`S` into the constraint `ScalarAffineFunction{T}`-in-`S`. """ -struct ScalarFunctionizeBridge{T, S} <: AbstractFunctionConversionBridge +struct ScalarFunctionizeBridge{T, S} <: AbstractFunctionConversionBridge{MOI.ScalarAffineFunction{T}, S} constraint::CI{MOI.ScalarAffineFunction{T}, S} end function bridge_constraint(::Type{ScalarFunctionizeBridge{T, S}}, model, @@ -58,7 +58,7 @@ end The `VectorFunctionizeBridge` converts a constraint `VectorOfVariables`-in-`S` into the constraint `VectorAffineFunction{T}`-in-`S`. """ -mutable struct VectorFunctionizeBridge{T, S} <: AbstractFunctionConversionBridge +mutable struct VectorFunctionizeBridge{T, S} <: AbstractFunctionConversionBridge{MOI.VectorAffineFunction{T}, S} constraint::CI{MOI.VectorAffineFunction{T}, S} end function bridge_constraint(::Type{VectorFunctionizeBridge{T, S}}, model, diff --git a/src/Bridges/Constraint/norm_to_lp.jl b/src/Bridges/Constraint/norm_to_lp.jl index 729bfce602..391cca2cc5 100644 --- a/src/Bridges/Constraint/norm_to_lp.jl +++ b/src/Bridges/Constraint/norm_to_lp.jl @@ -5,67 +5,63 @@ The `NormInfinityCone` is representable with LP constraints, since ``t \\ge \\max_i \\lvert x_i \\rvert`` if and only if ``t \\ge x_i`` and ``t \\ge -x_i`` for all ``i``. """ -struct NormInfinityBridge{T, F, G} <: AbstractBridge - nn_index::CI{F, MOI.Nonnegatives} +struct NormInfinityBridge{T, F, G} <: SetMapBridge{T, MOI.Nonnegatives, MOI.NormInfinityCone, F, G} + constraint::CI{F, MOI.Nonnegatives} end -function bridge_constraint(::Type{NormInfinityBridge{T, F, G}}, model::MOI.ModelLike, f::MOI.AbstractVectorFunction, s::MOI.NormInfinityCone) where {T, F, G} - f_scalars = MOIU.eachscalar(f) - t = f_scalars[1] - d = MOI.dimension(s) - lb = f_scalars[2:d] - ub = MOIU.operate(-, T, lb) - f_new = MOIU.operate(vcat, T, ub, lb) - for i in 1:MOI.output_dimension(f_new) - MOIU.operate_output_index!(+, T, i, f_new, t) - end - nn_index = MOI.add_constraint(model, f_new, MOI.Nonnegatives(MOI.output_dimension(f_new))) - return NormInfinityBridge{T, F, G}(nn_index) -end - -MOI.supports_constraint(::Type{NormInfinityBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormInfinityCone}) where T = true -MOIB.added_constrained_variable_types(::Type{<:NormInfinityBridge}) = Tuple{DataType}[] -MOIB.added_constraint_types(::Type{NormInfinityBridge{T, F, G}}) where {T, F, G} = [(F, MOI.Nonnegatives)] function concrete_bridge_type(::Type{<:NormInfinityBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormInfinityCone}) where T F = MOIU.promote_operation(+, T, G, G) return NormInfinityBridge{T, F, G} end -# Attributes, Bridge acting as a model -MOI.get(b::NormInfinityBridge{T, F, G}, ::MOI.NumberOfConstraints{F, MOI.Nonnegatives}) where {T, F, G} = 1 -MOI.get(b::NormInfinityBridge{T, F, G}, ::MOI.ListOfConstraintIndices{F, MOI.Nonnegatives}) where {T, F, G} = [b.nn_index] - -# References -MOI.delete(model::MOI.ModelLike, c::NormInfinityBridge) = MOI.delete(model, c.nn_index) - -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::NormInfinityBridge{T, F, G}) where {T, F, G} - nn_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), c.nn_index)) - t = MOIU.operate!(/, T, sum(nn_func), T(length(nn_func))) - d = div(length(nn_func), 2) - x = MOIU.operate!(/, T, MOIU.operate!(-, T, nn_func[(d + 1):end], nn_func[1:d]), T(2)) - return MOIU.convert_approx(G, MOIU.operate(vcat, T, t, x)) +function map_set(::Type{<:NormInfinityBridge}, set::MOI.NormInfinityCone) + return MOI.Nonnegatives(2 * (MOI.dimension(set) - 1)) end -function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, c::NormInfinityBridge) - dim = 1 + div(MOI.dimension(MOI.get(model, MOI.ConstraintSet(), c.nn_index)), 2) - return MOI.NormInfinityCone(dim) +function inverse_map_set(::Type{<:NormInfinityBridge}, set::MOI.Nonnegatives) + return MOI.NormInfinityCone(div(MOI.dimension(set), 2) + 1) end -function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintPrimal, c::NormInfinityBridge) - nn_primal = MOI.get(model, MOI.ConstraintPrimal(), c.nn_index) - t = sum(nn_primal) / length(nn_primal) - d = div(length(nn_primal), 2) - x = (nn_primal[(d + 1):end] - nn_primal[1:d]) / 2 - return vcat(t, x) + +function map_function(::Type{<:NormInfinityBridge{T}}, func) where T + scalars = MOIU.eachscalar(func) + t = scalars[1] + lb = scalars[2:end] + ub = MOIU.operate(-, T, lb) + f_new = MOIU.operate(vcat, T, ub, lb) + for i in 1:(2 * (length(scalars) - 1)) + MOIU.operate_output_index!(+, T, i, f_new, t) + end + return f_new +end +function inverse_map_function(::Type{<:NormInfinityBridge{T}}, func) where T + scalars = MOIU.eachscalar(func) + t = MOIU.operate!(/, T, sum(scalars), T(length(scalars))) + d = div(length(scalars), 2) + x = MOIU.operate!(/, T, MOIU.operate!(-, T, scalars[(d + 1):end], scalars[1:d]), T(2)) + return MOIU.operate(vcat, T, t, x) end # Given a_i is dual on t - x_i >= 0 and b_i is dual on t + x_i >= 0, # the dual on (t, x) in NormInfinityCone is (u, v) in NormOneCone, where # v_i = -a_i + b_i and u = sum(a) + sum(b). -function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintDual, c::NormInfinityBridge) - nn_dual = MOI.get(model, MOI.ConstraintDual(), c.nn_index) - t = sum(nn_dual) - d = div(length(nn_dual), 2) - x = (nn_dual[(d + 1):end] - nn_dual[1:d]) +function adjoint_map_function(::Type{<:NormInfinityBridge}, func) + scalars = MOIU.eachscalar(func) + t = sum(scalars) + d = div(length(scalars), 2) + x = (scalars[(d + 1):end] - scalars[1:d]) return vcat(t, x) end +function inverse_adjoint_map_function(::Type{<:NormInfinityBridge{T}}, func::AbstractVector{T}) where T + # This is used by `MOI.ConstraintDualStart`. + # The result should belong to `MOI.Nonnegatives` and the sum of the elements should + # be `t`. + # Only one of dual of the upper and lower bound should be active so one of the two + # duals is zero. We know which one since they should be nonnegative. + # Then if `t = sum abs(x_i)`, we will indeed have only one of them being zero. + t = func[1] + y = func[2:end] + lb = [x > 0 ? x : zero(x) for x in y] + ub = [x < 0 ? -x : zero(x) for x in y] + x = [ub; lb] + return x .+ (t - sum(x)) / length(x) +end """ NormOneBridge{T} diff --git a/src/Bridges/Constraint/rsoc.jl b/src/Bridges/Constraint/rsoc.jl index 1e75c9a8b5..03592c86ab 100644 --- a/src/Bridges/Constraint/rsoc.jl +++ b/src/Bridges/Constraint/rsoc.jl @@ -16,95 +16,70 @@ That means in particular that the norm is of constraint primal and duals are pre [1] Ben-Tal, Aharon, and Arkadi Nemirovski. *Lectures on modern convex optimization: analysis, algorithms, and engineering applications*. Society for Industrial and Applied Mathematics, 2001. """ -struct RSOCBridge{T, F, G} <: AbstractBridge - soc::CI{F, MOI.SecondOrderCone} -end -function rotate_function(f::MOI.AbstractVectorFunction, T::Type) - d = MOI.output_dimension(f) - f_scalars = MOIU.eachscalar(f) - t = f_scalars[1] - u = f_scalars[2] - x = f_scalars[3:d] - s2 = √T(2) - ts = MOIU.operate!(/, T, t, s2) - us = MOIU.operate!(/, T, u, s2) - # Cannot use `operate!` here since `ts` and `us` are needed for the next - # line - y = ts - us - z = MOIU.operate!(+, T, ts, us) - return MOIU.operate(vcat, T, z, y, x) -end -function bridge_constraint(::Type{RSOCBridge{T, F, G}}, model, - f::MOI.AbstractVectorFunction, - s::MOI.RotatedSecondOrderCone) where {T, F, G} - soc = MOI.add_constraint(model, rotate_function(f, T), - MOI.SecondOrderCone(MOI.dimension(s))) - return RSOCBridge{T, F, G}(soc) +struct RSOCBridge{T, F, G} <: SetMapBridge{T, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, F, G} + constraint::CI{F, MOI.SecondOrderCone} end -function MOI.supports_constraint(::Type{RSOCBridge{T}}, - ::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.RotatedSecondOrderCone}) where T - return true -end -MOIB.added_constrained_variable_types(::Type{<:RSOCBridge}) = Tuple{DataType}[] -function MOIB.added_constraint_types(::Type{<:RSOCBridge{T, F}}) where {T, F} - return [(F, MOI.SecondOrderCone)] +function rotate_function_type(G::Type{<:MOI.AbstractVectorFunction}, T::Type) + S = MOIU.promote_operation(/, T, MOIU.scalar_type(G), T) + Y = MOIU.promote_operation(-, T, S, S) + Z = MOIU.promote_operation(+, T, S, S) + return MOIU.promote_operation(vcat, T, Z, Y, G) end + function concrete_bridge_type(::Type{<:RSOCBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.RotatedSecondOrderCone}) where T - S = MOIU.promote_operation(/, T, MOIU.scalar_type(G), T) - Y = MOIU.promote_operation(-, T, S, S) - Z = MOIU.promote_operation(+, T, S, S) - F = MOIU.promote_operation(vcat, T, Z, Y, G) - return RSOCBridge{T, F, G} + return RSOCBridge{T, rotate_function_type(G, T), G} end -# Attributes, Bridge acting as a model -function MOI.get(b::RSOCBridge{T, F}, - ::MOI.NumberOfConstraints{F, MOI.SecondOrderCone}) where {T, F} - return 1 +function map_set(::Type{<:RSOCBridge}, set::MOI.RotatedSecondOrderCone) + return MOI.SecondOrderCone(MOI.dimension(set)) end -function MOI.get(b::RSOCBridge{T, F}, - ::MOI.ListOfConstraintIndices{F, MOI.SecondOrderCone}) where {T, F} - return [b.soc] +function inverse_map_set(::Type{<:RSOCBridge}, set::MOI.SecondOrderCone) + return MOI.RotatedSecondOrderCone(MOI.dimension(set)) end -# References -function MOI.delete(model::MOI.ModelLike, bridge::RSOCBridge) - MOI.delete(model, bridge.soc) +""" + SOCRBridge{T, F, G} + +We simply do the inverse transformation of [`RSOCBridge`](@ref). In fact, as the +transformation is an involution, we do the same transformation. +""" +struct SOCRBridge{T, F, G} <: SetMapBridge{T, MOI.RotatedSecondOrderCone, MOI.SecondOrderCone, F, G} + constraint::CI{F, MOI.RotatedSecondOrderCone} end -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, - bridge::RSOCBridge{T, F, G}) where {T, F, G} - # As it is an involution, we can just reapply the same transformation - func = MOI.get(model, attr, bridge.soc) - return MOIU.convert_approx(G, rotate_function(func, T)) +function concrete_bridge_type(::Type{<:SOCRBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.SecondOrderCone}) where T + return SOCRBridge{T, rotate_function_type(G, T), G} end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, bridge::RSOCBridge) - set = MOI.get(model, attr, bridge.soc) + +function map_set(::Type{<:SOCRBridge}, set::MOI.SecondOrderCone) return MOI.RotatedSecondOrderCone(MOI.dimension(set)) end -# As the linear transformation is a symmetric involution, -# the constraint primal and dual both need to be processed by reapplying the -# same transformation -function rotate_result(model, - attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, - ci::MOI.ConstraintIndex) - x = MOI.get(model, attr, ci) - T = eltype(x) - s2 = √T(2) - return [x[1]/s2 + x[2]/s2; x[1]/s2 - x[2]/s2; x[3:end]] -end -# Need to define both `get` methods and redirect to `rotate_result` to avoid -# ambiguity in dispatch -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, - bridge::RSOCBridge) - return rotate_result(model, attr, bridge.soc) +function inverse_map_set(::Type{<:SOCRBridge}, set::MOI.RotatedSecondOrderCone) + return MOI.SecondOrderCone(MOI.dimension(set)) end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual, - bridge::RSOCBridge) - return rotate_result(model, attr, bridge.soc) + +function map_function(::Type{<:Union{RSOCBridge{T}, SOCRBridge{T}}}, func) where T + scalars = MOIU.eachscalar(func) + t = scalars[1] + u = scalars[2] + x = scalars[3:end] + s2 = √T(2) + ts = MOIU.operate!(/, T, t, s2) + us = MOIU.operate!(/, T, u, s2) + # Cannot use `operate!` here since `ts` and `us` are needed for the next + # line + y = ts - us + z = MOIU.operate!(+, T, ts, us) + return MOIU.operate(vcat, T, z, y, x) end +# The map is an involution +inverse_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) +# The map is symmetric +adjoint_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) +# The map is a symmetric involution +inverse_adjoint_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) diff --git a/src/Bridges/Constraint/set_map.jl b/src/Bridges/Constraint/set_map.jl new file mode 100644 index 0000000000..50c6e08e88 --- /dev/null +++ b/src/Bridges/Constraint/set_map.jl @@ -0,0 +1,99 @@ +abstract type SetMapBridge{T, S2, S1, F, G} <: AbstractBridge end + +function bridge_constraint( + BT::Type{<:SetMapBridge{T, S2, S1, F, G}}, model::MOI.ModelLike, + func::G, set::S1) where {T, S2, S1, F, G} + mapped_func = map_function(BT, func) + constraint = MOI.add_constraint(model, map_function(BT, func), map_set(BT, set)) + return BT(constraint) +end + +function MOI.supports_constraint( + ::Type{<:SetMapBridge{T, S2, S1}}, ::Type{<:MOI.AbstractScalarFunction}, + ::Type{S1}) where {T, S2, S1<:MOI.AbstractScalarSet} + return true +end +function MOI.supports_constraint( + ::Type{<:SetMapBridge{T, S2, S1}}, ::Type{<:MOI.AbstractVectorFunction}, + ::Type{S1}) where {T, S2, S1<:MOI.AbstractVectorSet} + return true +end +MOIB.added_constrained_variable_types(::Type{<:SetMapBridge}) = Tuple{DataType}[] +function MOIB.added_constraint_types(::Type{<:SetMapBridge{T, S2, S1, F}}) where {T, S2, S1, F} + return [(F, S2)] +end + +# Attributes, Bridge acting as a model +function MOI.get(::SetMapBridge{T, S2, S1, F}, + ::MOI.NumberOfConstraints{F, S2}) where {T, S2, S1, F} + return 1 +end +function MOI.get(bridge::SetMapBridge{T, S2, S1, F}, + ::MOI.ListOfConstraintIndices{F, S2}) where {T, S2, S1, F} + return [bridge.constraint] +end + +# References +function MOI.delete(model::MOI.ModelLike, bridge::SetMapBridge) + MOI.delete(model, bridge.constraint) +end + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + bridge::SetMapBridge{T, S2, S1, F, G}) where {T, S2, S1, F, G} + mapped_func = MOI.get(model, attr, bridge.constraint) + func = inverse_map_function(typeof(bridge), mapped_func) + return MOIU.convert_approx(G, func) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, bridge::SetMapBridge) + set = MOI.get(model, attr, bridge.constraint) + return inverse_map_set(typeof(bridge), set) +end +function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, + bridge::SetMapBridge{T, S2, S1}, new_set::S1) where {T, S2, S1} + MOI.set(model, attr, bridge.constraint, map_set(typeof(bridge), new_set)) +end + +function MOI.supports( + model::MOI.ModelLike, + attr::Union{MOI.ConstraintPrimalStart, MOI.ConstraintDualStart}, + ::Type{<:SetMapBridge{T, S2, S1, F}}) where {T, S2, S1, F} + + return MOI.supports(model, attr, MOI.ConstraintIndex{F, S2}) +end +function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart}, + bridge::SetMapBridge) + value = MOI.get(model, attr, bridge.constraint) + return inverse_map_function(typeof(bridge), value) +end +function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart, + bridge::SetMapBridge, value) + mapped_value = map_function(typeof(bridge), value) + MOI.set(model, attr, bridge.constraint, mapped_value) +end +function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart}, + bridge::SetMapBridge) + value = MOI.get(model, attr, bridge.constraint) + return adjoint_map_function(typeof(bridge), value) +end +function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintDualStart, + bridge::SetMapBridge, value) + mapped_value = inverse_adjoint_map_function(typeof(bridge), value) + MOI.set(model, attr, bridge.constraint, mapped_value) +end + +function MOI.modify(model::MOI.ModelLike, bridge::SetMapBridge, + change::MOI.VectorConstantChange) + # By linearity of the map, we can just change the constant + constant = map_function(typeof(bridge), change.new_constant) + MOI.modify(model, bridge.constraint, MOI.VectorConstantChange(constant)) +end + +include("flip_sign.jl") +const GreaterToLess{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GreaterToLessBridge{T}, OT} +const LessToGreater{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LessToGreaterBridge{T}, OT} +const NonnegToNonpos{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonnegToNonposBridge{T}, OT} +const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT} +include("rsoc.jl") +const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT} +const SOCR{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCRBridge{T}, OT} diff --git a/src/Bridges/Constraint/socr.jl b/src/Bridges/Constraint/socr.jl deleted file mode 100644 index daebc2b7be..0000000000 --- a/src/Bridges/Constraint/socr.jl +++ /dev/null @@ -1,72 +0,0 @@ -""" - SOCRBridge{T, F, G} - -We simply do the inverse transformation of [`RSOCBridge`](@ref). In fact, as the -transformation is an involution, we do the same transformation. -""" -struct SOCRBridge{T, F, G} <: AbstractBridge - rsoc::CI{F, MOI.RotatedSecondOrderCone} -end -function bridge_constraint(::Type{SOCRBridge{T, F, G}}, model, - f::MOI.AbstractVectorFunction, - s::MOI.SecondOrderCone) where {T, F, G} - soc = MOI.add_constraint(model, rotate_function(f, T), - MOI.RotatedSecondOrderCone(MOI.dimension(s))) - return SOCRBridge{T, F, G}(soc) -end - -function MOI.supports_constraint(::Type{SOCRBridge{T}}, - ::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.SecondOrderCone}) where T - return true -end -MOIB.added_constrained_variable_types(::Type{<:SOCRBridge}) = Tuple{DataType}[] -function MOIB.added_constraint_types(::Type{<:SOCRBridge{T, F}}) where {T, F} - return [(F, MOI.RotatedSecondOrderCone)] -end -function concrete_bridge_type(::Type{<:SOCRBridge{T}}, - G::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.SecondOrderCone}) where T - S = MOIU.promote_operation(/, T, MOIU.scalar_type(G), T) - Y = MOIU.promote_operation(-, T, S, S) - Z = MOIU.promote_operation(+, T, S, S) - F = MOIU.promote_operation(vcat, T, Z, Y, G) - return SOCRBridge{T, F, G} -end - -# Attributes, Bridge acting as an model -function MOI.get(b::SOCRBridge{T, F}, - ::MOI.NumberOfConstraints{F, MOI.RotatedSecondOrderCone}) where {T, F} - return 1 -end -function MOI.get(b::SOCRBridge{T, F}, - ::MOI.ListOfConstraintIndices{F, MOI.RotatedSecondOrderCone}) where {T, F} - return [b.rsoc] -end - -# References -function MOI.delete(model::MOI.ModelLike, bridge::SOCRBridge) - MOI.delete(model, bridge.rsoc) -end - -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, - bridge::SOCRBridge{T, F, G}) where {T, F, G} - # As it is an involution, we can just reapply the same transformation - func = MOI.get(model, attr, bridge.rsoc) - return MOIU.convert_approx(G, rotate_function(func, T)) -end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, bridge::SOCRBridge) - set = MOI.get(model, attr, bridge.rsoc) - return MOI.SecondOrderCone(MOI.dimension(set)) -end -# As the linear transformation is a symmetric involution, -# the constraint primal and dual both need to be processed by reapplying the same transformation -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, - bridge::SOCRBridge) - return rotate_result(model, attr, bridge.rsoc) -end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual, - bridge::SOCRBridge) - return rotate_result(model, attr, bridge.rsoc) -end diff --git a/src/Bridges/bridge.jl b/src/Bridges/bridge.jl index 51c08dd3f4..5fe86a0e5d 100644 --- a/src/Bridges/bridge.jl +++ b/src/Bridges/bridge.jl @@ -61,6 +61,22 @@ function MOI.get(::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support accessing the attribute `$attr`.")) end +""" + function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, + bridge::AbstractBridge, value) + +Set the value of the attribute `attr` of the model `model` for the +constraint bridged by `bridge`. +""" +function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, + bridge::AbstractBridge, value) + if MOI.is_copyable(attr) && !MOI.supports(model, attr, typeof(bridge)) + throw(MOI.UnsupportedAttribute(attr)) + else + throw(MOI.SetAttributeNotAllowed(attr)) + end +end + """ added_constrained_variable_types(BT::Type{<:Variable.AbstractBridge})::Vector{Tuple{DataType}} diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 49c327c5f6..0e57d1c897 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -246,6 +246,7 @@ struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction} f::F end eachscalar(f::MOI.AbstractVectorFunction) = ScalarFunctionIterator(f) +eachscalar(f::AbstractVector) = f function Base.iterate(it::ScalarFunctionIterator, state = 1) if state > length(it) @@ -742,6 +743,8 @@ modified. """ function operate end +operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}...) where {T} = op(α...) + """ operate!(op::Function, ::Type{T}, args::Union{T, MOI.AbstractFunction}...)::MOI.AbstractFunction where T @@ -753,6 +756,8 @@ can be modified. The return type is the same than the method """ function operate! end +operate!(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}...) where {T} = op(α...) + """ operate_output_index!( op::Function, ::Type{T}, output_index::Integer, @@ -767,6 +772,10 @@ argument can be modified. """ function operate_output_index! end +function operate_output_index!(op::Function, ::Type{T}, i::Integer, x::Vector{T}, args...) where T + x[i] = operate!(op, T, x[i], args...) +end + """ promote_operation(op::Function, ::Type{T}, ArgsTypes::Type{<:Union{T, MOI.AbstractFunction}}...) where T diff --git a/src/sets.jl b/src/sets.jl index ba5b36171c..b60bc398d9 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -388,6 +388,16 @@ function dimension(set::AbstractSymmetricMatrixSetTriangle) return div(d * (d + 1), 2) end +""" + side_dimension_for_vectorized_dimension(n::Integer) + +Return the dimension `d` such that +`MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d))` is `n`. +""" +function side_dimension_for_vectorized_dimension(n::Base.Integer) + return div(isqrt(1 + 8n), 2) - 1 +end + """ abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end diff --git a/test/Bridges/Constraint/bridge.jl b/test/Bridges/Constraint/bridge.jl new file mode 100644 index 0000000000..4a2140c419 --- /dev/null +++ b/test/Bridges/Constraint/bridge.jl @@ -0,0 +1,20 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +struct DummyBridge <: MOIB.Constraint.AbstractBridge end + +@testset "AbstractBridge" begin + model = MOIU.Model{Float64}() + bridge = DummyBridge() + attr = MOI.ConstraintPrimalStart() + @test !MOI.supports(model, attr, typeof(bridge)) + @test_throws MOI.UnsupportedAttribute(attr) MOI.set(model, attr, bridge, 1.0) + attr = MOI.ConstraintFunction() + err = MOI.SetAttributeNotAllowed(attr) + @test_throws err MOI.set(model, attr, bridge, MOI.EqualTo(1.0)) +end diff --git a/test/Bridges/Constraint/flip_sign.jl b/test/Bridges/Constraint/flip_sign.jl index 89df791238..b4607267b5 100644 --- a/test/Bridges/Constraint/flip_sign.jl +++ b/test/Bridges/Constraint/flip_sign.jl @@ -8,7 +8,7 @@ const MOIB = MathOptInterface.Bridges include("../utilities.jl") -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) +mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) config = MOIT.TestConfig() @testset "GreaterToLess" begin @@ -22,12 +22,19 @@ config = MOIT.TestConfig() for S in [MOI.GreaterThan{Float64}]]) MOIU.set_mock_optimize!(mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0])) MOIT.linear6test(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + MOI.set(bridged_mock, attr, ci, 2.0) + @test MOI.get(bridged_mock, attr, ci) ≈ 2.0 + end + test_delete_bridge(bridged_mock, ci, 2, ((MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 1),)) @@ -72,6 +79,13 @@ end MOIT.solve_coef_scalaraffine_lessthan(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + MOI.set(bridged_mock, attr, ci, 2.0) + @test MOI.get(bridged_mock, attr, ci) ≈ 2.0 + end + test_delete_bridge(bridged_mock, ci, 1, ((MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0),)) @@ -88,9 +102,9 @@ end for S in [MOI.Nonnegatives]]) MOIU.set_mock_optimize!(mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0])) MOIT.linear7test(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}())) @@ -99,8 +113,8 @@ end MOI.Nonpositives, 1),)) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0], - (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[0, -2, 0]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]]) + (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[0.0, -2.0, 0.0]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3.0, -1.0]]) MOIT.lin1vtest(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MOI.Nonnegatives}())) func = MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) @@ -108,6 +122,13 @@ end new_func = MOI.VectorOfVariables(func.variables[[1, 3]]) @test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) == new_func @test MOI.get(bridged_mock, MOI.ConstraintSet(), ci) == MOI.Nonnegatives(2) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + MOI.set(bridged_mock, attr, ci, [1.0, 2.0]) + @test MOI.get(bridged_mock, attr, ci) ≈ [1.0, 2.0] + end + test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.Nonpositives, 0),)) end @@ -123,9 +144,9 @@ end for S in [MOI.Nonpositives]]) MOIU.set_mock_optimize!(mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, 0.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0])) MOIT.linear7test(bridged_mock, config) MOIU.set_mock_optimize!(mock, @@ -149,6 +170,13 @@ end MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + MOI.set(bridged_mock, attr, ci, [1.0, 2.0]) + @test MOI.get(bridged_mock, attr, ci) ≈ [1.0, 2.0] + end + test_delete_bridge(bridged_mock, ci, 1, ((MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0),)) diff --git a/test/Bridges/Constraint/functionize.jl b/test/Bridges/Constraint/functionize.jl index 5fccf22435..e715e7b067 100644 --- a/test/Bridges/Constraint/functionize.jl +++ b/test/Bridges/Constraint/functionize.jl @@ -58,6 +58,7 @@ config_with_basis = MOIT.TestConfig(basis = true) @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] for ci in cis + @test MOI.supports(bridged_mock, attr, typeof(ci)) MOI.set(bridged_mock, attr, ci, 2.0) @test MOI.get(bridged_mock, attr, ci) == 2.0 end @@ -126,6 +127,7 @@ end @test MOI.get(bridged_mock, MOI.ConstraintSet(), ci) == MOI.Nonnegatives(2) @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) MOI.set(bridged_mock, attr, ci, [1.0, 2.0]) @test MOI.get(bridged_mock, attr, ci) == [1.0, 2.0] end diff --git a/test/Bridges/Constraint/norm_to_lp.jl b/test/Bridges/Constraint/norm_to_lp.jl index f80c2312ef..d897b66be8 100644 --- a/test/Bridges/Constraint/norm_to_lp.jl +++ b/test/Bridges/Constraint/norm_to_lp.jl @@ -8,7 +8,7 @@ const MOIB = MathOptInterface.Bridges include("../utilities.jl") -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) +mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) config = MOIT.TestConfig() @testset "NormInfinity" begin @@ -73,6 +73,14 @@ config = MOIT.TestConfig() end ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + value = [4.0, 1.0, -2.0] + MOI.set(bridged_mock, attr, ci, value) + @test MOI.get(bridged_mock, attr, ci) ≈ value + end + test_delete_bridge(bridged_mock, ci, 3, ((MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0),)) end diff --git a/test/Bridges/Constraint/rsoc.jl b/test/Bridges/Constraint/rsoc.jl index 85441fe9cd..cb7d8e0717 100644 --- a/test/Bridges/Constraint/rsoc.jl +++ b/test/Bridges/Constraint/rsoc.jl @@ -8,7 +8,7 @@ const MOIB = MathOptInterface.Bridges include("../utilities.jl") -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) +mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) config = MOIT.TestConfig() @testset "RSOC" begin @@ -29,5 +29,48 @@ config = MOIT.TestConfig() (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3/2, 1/2, -1.0, -1.0]]) MOIT.rotatedsoc1ftest(bridged_mock, config) ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + value = [√2, 1/√2, -1.0, -1.0] + MOI.set(bridged_mock, attr, ci, value) + @test MOI.get(bridged_mock, attr, ci) ≈ value + end + test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone, 0),)) end + +@testset "SOCR" begin + bridged_mock = MOIB.Constraint.SOCR{Float64}(mock) + + MOIT.basic_constraint_tests( + bridged_mock, config, + include = [(F, S) + for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, + MOI.VectorQuadraticFunction{Float64}] + for S in [MOI.SecondOrderCone]]) + + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], + (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) + MOIT.soc1vtest(bridged_mock, config) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], + (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) + MOIT.soc1ftest(bridged_mock, config) + ci = first(MOI.get( + bridged_mock, + MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + value = [√2, 1/√2, -1.0, -1.0] + MOI.set(bridged_mock, attr, ci, value) + @test MOI.get(bridged_mock, attr, ci) ≈ value + end + + test_delete_bridge(bridged_mock, ci, 3, + ((MOI.VectorAffineFunction{Float64}, + MOI.RotatedSecondOrderCone, 0),)) +end diff --git a/test/Bridges/Constraint/socr.jl b/test/Bridges/Constraint/socr.jl deleted file mode 100644 index 31c2eafdfe..0000000000 --- a/test/Bridges/Constraint/socr.jl +++ /dev/null @@ -1,39 +0,0 @@ -using Test - -using MathOptInterface -const MOI = MathOptInterface -const MOIT = MathOptInterface.Test -const MOIU = MathOptInterface.Utilities -const MOIB = MathOptInterface.Bridges - -include("../utilities.jl") - -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) -config = MOIT.TestConfig() - -@testset "SOCR" begin - bridged_mock = MOIB.Constraint.SOCR{Float64}(mock) - - MOIT.basic_constraint_tests( - bridged_mock, config, - include = [(F, S) - for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, - MOI.VectorQuadraticFunction{Float64}] - for S in [MOI.SecondOrderCone]]) - - mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], - (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) - MOIT.soc1vtest(bridged_mock, config) - mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], - (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) - MOIT.soc1ftest(bridged_mock, config) - ci = first(MOI.get( - bridged_mock, - MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, - MOI.SecondOrderCone}())) - test_delete_bridge(bridged_mock, ci, 3, - ((MOI.VectorAffineFunction{Float64}, - MOI.RotatedSecondOrderCone, 0),)) -end From 7e3df9df80973020b9e0af90074f42079229a2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 28 Oct 2019 16:06:41 +0100 Subject: [PATCH 2/6] Refactor SOCtoPSD --- src/Bridges/Constraint/soc_to_psd.jl | 157 ++++++++------------------- 1 file changed, 46 insertions(+), 111 deletions(-) diff --git a/src/Bridges/Constraint/soc_to_psd.jl b/src/Bridges/Constraint/soc_to_psd.jl index 426e6418f0..0077ea92c5 100644 --- a/src/Bridges/Constraint/soc_to_psd.jl +++ b/src/Bridges/Constraint/soc_to_psd.jl @@ -5,8 +5,10 @@ Builds a VectorAffineFunction representing the upper (or lower) triangular part [ f[1] f[2:end]' ] [ f[2:end] g * I ] """ -function _SOCtoPSDaff(T::Type, F::Type{<:MOI.AbstractVectorFunction}, - f::MOI.AbstractVectorFunction, g::MOI.AbstractScalarFunction) +function _SOCtoPSDaff(T::Type, + f::MOI.AbstractVectorFunction, + g::MOI.AbstractScalarFunction) + F = MOIU.promote_operation(vcat, T, typeof(g), T) dim = MOI.output_dimension(f) n = div(dim * (dim+1), 2) h = MOIU.zero_with_output_dimension(F, n) @@ -46,26 +48,8 @@ as bridging second order cone constraints to semidefinite constraints can be achieved by the [`SOCRBridge`](@ref) followed by the [`RSOCtoPSDBridge`](@ref) while creating a smaller semidefinite constraint. """ -struct SOCtoPSDBridge{T, F, G} <: AbstractBridge - dim::Int - cr::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} -end -function bridge_constraint(::Type{SOCtoPSDBridge{T, F, G}}, model::MOI.ModelLike, g::G, - s::MOI.SecondOrderCone) where {T, F, G} - d = MOI.dimension(s) - f = _SOCtoPSDaff(T, F, g, MOIU.eachscalar(g)[1]) - cr = MOI.add_constraint(model, f, MOI.PositiveSemidefiniteConeTriangle(d)) - return SOCtoPSDBridge{T, F, G}(d, cr) -end - -function MOI.supports_constraint( - ::Type{<:SOCtoPSDBridge}, ::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.SecondOrderCone}) - return true -end -MOIB.added_constrained_variable_types(::Type{<:SOCtoPSDBridge}) = Tuple{DataType}[] -function MOIB.added_constraint_types(::Type{<:SOCtoPSDBridge{T, F}}) where {T, F} - return [(F, MOI.PositiveSemidefiniteConeTriangle)] +struct SOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.SecondOrderCone, MOI.PositiveSemidefiniteConeTriangle, F, G} + constraint::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} end function concrete_bridge_type(::Type{<:SOCtoPSDBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, @@ -74,44 +58,28 @@ function concrete_bridge_type(::Type{<:SOCtoPSDBridge{T}}, return SOCtoPSDBridge{T, F, G} end - -# Attributes, Bridge acting as a model -function MOI.get( - ::SOCtoPSDBridge{T, F}, - ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} - return 1 +function map_set(::Type{<:SOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) + return MOI.SecondOrderCone(MOI.side_dimension(set)) end -function MOI.get( - bridge::SOCtoPSDBridge{T}, - ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} - return [bridge.cr] +function inverse_map_set(::Type{<:SOCtoPSDBridge}, set::MOI.SecondOrderCone) + return MOI.PositiveSemidefiniteConeTriangle(MOI.dimension(set)) end -# References -function MOI.delete(model::MOI.ModelLike, c::SOCtoPSDBridge) - MOI.delete(model, c.cr) +function map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T + return _SOCtoPSDaff(T, g, MOIU.eachscalar(g)[1]) end - -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, - bridge::SOCtoPSDBridge{T, F, G}) where {T, F, G} - f = MOI.get(model, attr, bridge.cr) - g = MOIU.eachscalar(f)[trimap.(1, 1:bridge.dim)] - return MOIU.convert_approx(G, g) -end -function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::SOCtoPSDBridge) - return MOI.SecondOrderCone(bridge.dim) -end -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, c::SOCtoPSDBridge) - return MOI.get(model, a, c.cr)[trimap.(1, 1:c.dim)] +function inverse_map_function(::Type{<:SOCtoPSDBridge}, func) + scalars = MOIU.eachscalar(func) + dim = side_dimension_for_vectorized_dimension(length(scalars)) + return scalars[trimap.(1, 1:dim)] end -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::SOCtoPSDBridge) - dual = MOI.get(model, a, c.cr) - tdual = sum(i -> dual[trimap(i, i)], 1:c.dim) - return [tdual; dual[trimap.(2:c.dim, 1)]*2] +function adjoint_map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T + scalars = MOIU.eachscalar(func) + dim = side_dimension_for_vectorized_dimension(length(scalars)) + tdual = sum(i -> func[trimap(i, i)], 1:dim) + return MOIU.operate(vcat, T, tdual, func[trimap.(2:c.dim, 1)]*2) end - """ The `RSOCtoPSDBridge` transforms the second order cone constraint ``\\lVert x \\rVert \\le 2tu`` with ``u \\ge 0`` into the semidefinite cone constraints ```math @@ -135,29 +103,8 @@ which is equivalent to \\end{align*} ``` """ -struct RSOCtoPSDBridge{T, F, G} <: AbstractBridge - dim::Int - cr::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} -end -function bridge_constraint(::Type{RSOCtoPSDBridge{T, F, G}}, model::MOI.ModelLike, g::G, - set::MOI.RotatedSecondOrderCone) where {T, F, G} - dim = MOI.dimension(set) - 1 - g_scalars = MOIU.eachscalar(g) - h = MOIU.operate!(*, T, g_scalars[2], convert(T, 2)) - f = _SOCtoPSDaff(T, F, g_scalars[[1; 3:MOI.output_dimension(g)]], h) - cr = MOI.add_constraint(model, f, MOI.PositiveSemidefiniteConeTriangle(dim)) - return RSOCtoPSDBridge{T, F, G}(dim, cr) -end - - -function MOI.supports_constraint( - ::Type{<:RSOCtoPSDBridge}, ::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.RotatedSecondOrderCone}) - return true -end -MOIB.added_constrained_variable_types(::Type{<:RSOCtoPSDBridge}) = Tuple{DataType}[] -function MOIB.added_constraint_types(::Type{<:RSOCtoPSDBridge{T, F}}) where {T, F} - return [(F, MOI.PositiveSemidefiniteConeTriangle)] +struct RSOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.RotatedSecondOrderCone, MOI.PositiveSemidefiniteConeTriangle, F, G} + constraint::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} end function concrete_bridge_type(::Type{<:RSOCtoPSDBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, @@ -168,45 +115,33 @@ function concrete_bridge_type(::Type{<:RSOCtoPSDBridge{T}}, return RSOCtoPSDBridge{T, F, G} end -# Attributes, Bridge acting as a model -function MOI.get( - ::RSOCtoPSDBridge{T, F}, - ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} - return 1 +function map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) + return MOI.RotatedSecondOrderCone(MOI.side_dimension(set) + 1) end -function MOI.get( - bridge::RSOCtoPSDBridge{T}, - ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} - return [bridge.cr] +function inverse_map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.RotatedSecondOrderCone) + return MOI.PositiveSemidefiniteConeTriangle(MOI.dimension(set) - 1) end -# References -function MOI.delete(model::MOI.ModelLike, bridge::RSOCtoPSDBridge) - MOI.delete(model, bridge.cr) -end - -# Attributes, Bridge acting as a constraint -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, - bridge::RSOCtoPSDBridge{T, F, G}) where {T, F, G} - f_scalars = MOIU.eachscalar(MOI.get(model, attr, bridge.cr)) - t = f_scalars[1] - u = MOIU.operate!(/, T, f_scalars[3], convert(T, 2)) +function map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T + scalars = MOIU.eachscalar(func) + h = MOIU.operate!(*, T, scalars[2], convert(T, 2)) + return _SOCtoPSDaff(T, scalars[[1; 3:MOI.output_dimension(g)]], h) +end +function inverse_map_function(::Type{<:RSOCtoPSDBridge}, func) + scalars = MOIU.eachscalar(func) + dim = side_dimension_for_vectorized_dimension(length(scalars)) + t = scalars[1] + # It is (2u*I)[1,1] so it needs to be divided by 2 to get u + u = MOIU.operate!(/, T, scalars[3], convert(T, 2)) funcs = [t, u] - for i in 2:bridge.dim - push!(funcs, f_scalars[trimap(1, i)]) + for i in 2:dim + push!(funcs, scalars[trimap(1, i)]) end - return MOIU.convert_approx(G, MOIU.vectorize(funcs)) -end -function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::RSOCtoPSDBridge) - return MOI.RotatedSecondOrderCone(bridge.dim + 1) -end -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, bridge::RSOCtoPSDBridge) - x = MOI.get(model, MOI.ConstraintPrimal(), bridge.cr)[[trimap(1, 1); trimap(2, 2); trimap.(2:bridge.dim, 1)]] - x[2] /= 2 # It is (2u*I)[1,1] so it needs to be divided by 2 to get u - return x + return MOIU.vectorize(funcs) end -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, bridge::RSOCtoPSDBridge) - dual = MOI.get(model, MOI.ConstraintDual(), bridge.cr) - udual = sum(i -> dual[trimap(i, i)], 2:bridge.dim) - return [dual[1]; 2udual; dual[trimap.(2:bridge.dim, 1)]*2] +function adjoint_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T + scalars = MOIU.eachscalar(func) + dim = side_dimension_for_vectorized_dimension(length(scalars)) + udual = sum(i -> func[trimap(i, i)], 2:dim) + return MOIU.operate(vcat, T, dual[1], 2udual, func[trimap.(2:dim, 1)]*2) end From cb600bede3965ea021c5e887ffcade4981199c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 28 Oct 2019 17:03:10 +0100 Subject: [PATCH 3/6] Fix Julia v1.1 StackOverflow --- src/Utilities/functions.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 0e57d1c897..e237dc9e1a 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -743,7 +743,10 @@ modified. """ function operate end -operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}...) where {T} = op(α...) +# With only one method with `α::Union{T, AbstractVector{T}}...`, Julia v1.1.1 +# fails at precompilation with a StackOverflowError. +operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}) where T = op(α) +operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}, β::Union{T, AbstractVector{T}}) where T = op(α, β) """ operate!(op::Function, ::Type{T}, From 32ed4edf7e826b97a1d68dc1bc5c2bc7e0083520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 28 Oct 2019 20:38:29 +0100 Subject: [PATCH 4/6] Fix SOCtoPSD bridges --- src/Bridges/Constraint/soc_to_psd.jl | 44 +++++++++++++--------------- src/Utilities/functions.jl | 1 + src/Utilities/sets.jl | 10 +++++++ src/sets.jl | 10 ------- test/Utilities/sets.jl | 13 +++++++- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/Bridges/Constraint/soc_to_psd.jl b/src/Bridges/Constraint/soc_to_psd.jl index 0077ea92c5..c452569d3d 100644 --- a/src/Bridges/Constraint/soc_to_psd.jl +++ b/src/Bridges/Constraint/soc_to_psd.jl @@ -48,7 +48,7 @@ as bridging second order cone constraints to semidefinite constraints can be achieved by the [`SOCRBridge`](@ref) followed by the [`RSOCtoPSDBridge`](@ref) while creating a smaller semidefinite constraint. """ -struct SOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.SecondOrderCone, MOI.PositiveSemidefiniteConeTriangle, F, G} +struct SOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.PositiveSemidefiniteConeTriangle, MOI.SecondOrderCone, F, G} constraint::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} end function concrete_bridge_type(::Type{<:SOCtoPSDBridge{T}}, @@ -58,26 +58,26 @@ function concrete_bridge_type(::Type{<:SOCtoPSDBridge{T}}, return SOCtoPSDBridge{T, F, G} end -function map_set(::Type{<:SOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) - return MOI.SecondOrderCone(MOI.side_dimension(set)) -end -function inverse_map_set(::Type{<:SOCtoPSDBridge}, set::MOI.SecondOrderCone) +function map_set(::Type{<:SOCtoPSDBridge}, set::MOI.SecondOrderCone) return MOI.PositiveSemidefiniteConeTriangle(MOI.dimension(set)) end +function inverse_map_set(::Type{<:SOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) + return MOI.SecondOrderCone(MOI.side_dimension(set)) +end function map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T - return _SOCtoPSDaff(T, g, MOIU.eachscalar(g)[1]) + return _SOCtoPSDaff(T, func, MOIU.eachscalar(func)[1]) end function inverse_map_function(::Type{<:SOCtoPSDBridge}, func) scalars = MOIU.eachscalar(func) - dim = side_dimension_for_vectorized_dimension(length(scalars)) + dim = MOIU.side_dimension_for_vectorized_dimension(length(scalars)) return scalars[trimap.(1, 1:dim)] end function adjoint_map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) - dim = side_dimension_for_vectorized_dimension(length(scalars)) + dim = MOIU.side_dimension_for_vectorized_dimension(length(scalars)) tdual = sum(i -> func[trimap(i, i)], 1:dim) - return MOIU.operate(vcat, T, tdual, func[trimap.(2:c.dim, 1)]*2) + return MOIU.operate(vcat, T, tdual, func[trimap.(2:dim, 1)]*2) end """ @@ -103,7 +103,7 @@ which is equivalent to \\end{align*} ``` """ -struct RSOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.RotatedSecondOrderCone, MOI.PositiveSemidefiniteConeTriangle, F, G} +struct RSOCtoPSDBridge{T, F, G} <: SetMapBridge{T, MOI.PositiveSemidefiniteConeTriangle, MOI.RotatedSecondOrderCone, F, G} constraint::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} end function concrete_bridge_type(::Type{<:RSOCtoPSDBridge{T}}, @@ -115,33 +115,29 @@ function concrete_bridge_type(::Type{<:RSOCtoPSDBridge{T}}, return RSOCtoPSDBridge{T, F, G} end -function map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) - return MOI.RotatedSecondOrderCone(MOI.side_dimension(set) + 1) -end -function inverse_map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.RotatedSecondOrderCone) +function map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.RotatedSecondOrderCone) return MOI.PositiveSemidefiniteConeTriangle(MOI.dimension(set) - 1) end +function inverse_map_set(::Type{<:RSOCtoPSDBridge}, set::MOI.PositiveSemidefiniteConeTriangle) + return MOI.RotatedSecondOrderCone(MOI.side_dimension(set) + 1) +end function map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) h = MOIU.operate!(*, T, scalars[2], convert(T, 2)) - return _SOCtoPSDaff(T, scalars[[1; 3:MOI.output_dimension(g)]], h) + return _SOCtoPSDaff(T, scalars[[1; 3:MOI.output_dimension(func)]], h) end -function inverse_map_function(::Type{<:RSOCtoPSDBridge}, func) +function inverse_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) - dim = side_dimension_for_vectorized_dimension(length(scalars)) + dim = MOIU.side_dimension_for_vectorized_dimension(length(scalars)) t = scalars[1] # It is (2u*I)[1,1] so it needs to be divided by 2 to get u u = MOIU.operate!(/, T, scalars[3], convert(T, 2)) - funcs = [t, u] - for i in 2:dim - push!(funcs, scalars[trimap(1, i)]) - end - return MOIU.vectorize(funcs) + return MOIU.operate(vcat, T, t, u, scalars[[trimap(1, i) for i in 2:dim]]) end function adjoint_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) - dim = side_dimension_for_vectorized_dimension(length(scalars)) + dim = MOIU.side_dimension_for_vectorized_dimension(length(scalars)) udual = sum(i -> func[trimap(i, i)], 2:dim) - return MOIU.operate(vcat, T, dual[1], 2udual, func[trimap.(2:dim, 1)]*2) + return MOIU.operate(vcat, T, func[1], 2udual, func[trimap.(2:dim, 1)]*2) end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index e237dc9e1a..bf1c438c04 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -747,6 +747,7 @@ function operate end # fails at precompilation with a StackOverflowError. operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}) where T = op(α) operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}, β::Union{T, AbstractVector{T}}) where T = op(α, β) +operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}, β::Union{T, AbstractVector{T}}, γ::Union{T, AbstractVector{T}}) where T = op(α, β, γ) """ operate!(op::Function, ::Type{T}, diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index 63330d5b32..f7373b4d8b 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -30,3 +30,13 @@ vector_set_type(::Type{<:MOI.GreaterThan}) = MOI.Nonnegatives scalar_set_type(::Type{<:MOI.Zeros}, T::Type) = MOI.EqualTo{T} scalar_set_type(::Type{<:MOI.Nonpositives}, T::Type) = MOI.LessThan{T} scalar_set_type(::Type{<:MOI.Nonnegatives}, T::Type) = MOI.GreaterThan{T} + +""" + side_dimension_for_vectorized_dimension(n::Integer) + +Return the dimension `d` such that +`MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d))` is `n`. +""" +function side_dimension_for_vectorized_dimension(n::Base.Integer) + return div(isqrt(1 + 8n), 2) +end diff --git a/src/sets.jl b/src/sets.jl index b60bc398d9..ba5b36171c 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -388,16 +388,6 @@ function dimension(set::AbstractSymmetricMatrixSetTriangle) return div(d * (d + 1), 2) end -""" - side_dimension_for_vectorized_dimension(n::Integer) - -Return the dimension `d` such that -`MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d))` is `n`. -""" -function side_dimension_for_vectorized_dimension(n::Base.Integer) - return div(isqrt(1 + 8n), 2) - 1 -end - """ abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end diff --git a/test/Utilities/sets.jl b/test/Utilities/sets.jl index 9397b7f59e..272dc96357 100644 --- a/test/Utilities/sets.jl +++ b/test/Utilities/sets.jl @@ -1,4 +1,15 @@ -using SparseArrays +using SparseArrays, Test +using MathOptInterface +const MOI = MathOptInterface +const MOIU = MOI.Utilities + +@testset "Side dimension" begin + for side_dim in 1:10 + set = MOI.PositiveSemidefiniteConeTriangle(side_dim) + vec_dim = MOI.dimension(set) + @test MOIU.side_dimension_for_vectorized_dimension(vec_dim) == side_dim + end +end @testset "Constant" begin @test MOI.constant(MOI.EqualTo(3)) == 3 From e3a1702c8586777a164d36e18c469b4fcdce0227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 29 Oct 2019 15:55:57 +0100 Subject: [PATCH 5/6] Implement starting values for SOCtoPSD --- src/Bridges/Constraint/soc_to_psd.jl | 48 +++++++++++++++++++++++---- src/Utilities/functions.jl | 18 +++++----- test/Bridges/Constraint/soc_to_psd.jl | 20 ++++++++++- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/Bridges/Constraint/soc_to_psd.jl b/src/Bridges/Constraint/soc_to_psd.jl index c452569d3d..f73f2f3fc0 100644 --- a/src/Bridges/Constraint/soc_to_psd.jl +++ b/src/Bridges/Constraint/soc_to_psd.jl @@ -5,14 +5,14 @@ Builds a VectorAffineFunction representing the upper (or lower) triangular part [ f[1] f[2:end]' ] [ f[2:end] g * I ] """ -function _SOCtoPSDaff(T::Type, - f::MOI.AbstractVectorFunction, - g::MOI.AbstractScalarFunction) +function _SOCtoPSDaff(::Type{T}, + f::Union{MOI.AbstractVectorFunction, AbstractVector{T}}, + g::Union{MOI.AbstractScalarFunction, T}) where T F = MOIU.promote_operation(vcat, T, typeof(g), T) - dim = MOI.output_dimension(f) + f_scalars = MOIU.eachscalar(f) + dim = length(f_scalars) n = div(dim * (dim+1), 2) h = MOIU.zero_with_output_dimension(F, n) - f_scalars = MOIU.eachscalar(f) MOIU.operate_output_index!(+, T, trimap(1, 1), h, f_scalars[1]) for i in 2:dim MOIU.operate_output_index!(+, T, trimap(1, i), h, f_scalars[i]) @@ -79,6 +79,22 @@ function adjoint_map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T tdual = sum(i -> func[trimap(i, i)], 1:dim) return MOIU.operate(vcat, T, tdual, func[trimap.(2:dim, 1)]*2) end +function inverse_adjoint_map_function(::Type{<:SOCtoPSDBridge{T}}, func) where T + # func is (t, x) such that x'x ≤ t^2 and we need to find a PSD matrix + # [a x'/2] + # [x/2 Q ] + # such that a + tr(Q) == t + # By choosing a = t/2 and Q = t/(2x'x) * xx', we get + # a + tr(Q) = t/2 + t/(2x'x) * tr(xx') = t/2 + t/(2x'x) * tr(x'x) = t + # Moreover, by the Schur complement, the matrix is PSD iff + # xx'/(2t) ⪯ t/(2x'x) * xx' + # 1/(2t) ≤ t/(2x'x) + # x'x ≤ t^2 + # which is the SOC inequality + t = func[1] + x = func[2:end] + return inverse_adjoint_map_function(RSOCtoPSDBridge{T}, [t/2; t; x]) +end """ The `RSOCtoPSDBridge` transforms the second order cone constraint ``\\lVert x \\rVert \\le 2tu`` with ``u \\ge 0`` into the semidefinite cone constraints @@ -125,7 +141,7 @@ end function map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) h = MOIU.operate!(*, T, scalars[2], convert(T, 2)) - return _SOCtoPSDaff(T, scalars[[1; 3:MOI.output_dimension(func)]], h) + return _SOCtoPSDaff(T, scalars[[1; 3:length(scalars)]], h) end function inverse_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T scalars = MOIU.eachscalar(func) @@ -141,3 +157,23 @@ function adjoint_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T udual = sum(i -> func[trimap(i, i)], 2:dim) return MOIU.operate(vcat, T, func[1], 2udual, func[trimap.(2:dim, 1)]*2) end +function inverse_adjoint_map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where T + # func is (t, u, x) such that x'x ≤ 2tu and we need to find a PSD matrix + # [t x'/2] + # [x/2 Q ] + # such that 2tr(Q) == u + # By choosing Q = u/(2x'x) * xx', we get + # 2tr(Q) = u/(x'x) * tr(xx') = u/(x'x) * tr(x'x) = u + # Moreover, by the Schur complement, the matrix is PSD iff + # xx'/(4t) ⪯ u/(2x'x) * xx' + # 1/(4t) ≤ u/(2x'x) + # x'x ≤ 2tu + # which is the RSOC inequality + t = func[1] + u = func[2] + x = func[3:end] + Q = (x * x') * (u / (2 * (x' * x))) + M = [t x'/2 + x/2 Q] + return [M[i, j] for j in 1:size(M, 2) for i in 1:j] +end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index bf1c438c04..601b9778ef 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -313,12 +313,15 @@ function Base.getindex(it::ScalarFunctionIterator{VQF{T}}, I::AbstractVector) wh return VQF(affine_terms, quadratic_terms, constant) end -function zero_with_output_dimension(::Type{<:MOI.VectorAffineFunction{T}}, n::Integer) where T +function zero_with_output_dimension(::Type{Vector{T}}, n::Integer) where T + return zeros(T, n) +end +function zero_with_output_dimension(::Type{MOI.VectorAffineFunction{T}}, n::Integer) where T return MOI.VectorAffineFunction{T}( MOI.VectorAffineTerm{T}[], zeros(T, n)) end -function zero_with_output_dimension(::Type{<:MOI.VectorQuadraticFunction{T}}, n::Integer) where T +function zero_with_output_dimension(::Type{MOI.VectorQuadraticFunction{T}}, n::Integer) where T return MOI.VectorQuadraticFunction{T}( MOI.VectorAffineTerm{T}[], MOI.VectorQuadraticTerm{T}[], @@ -743,11 +746,8 @@ modified. """ function operate end -# With only one method with `α::Union{T, AbstractVector{T}}...`, Julia v1.1.1 -# fails at precompilation with a StackOverflowError. -operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}) where T = op(α) -operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}, β::Union{T, AbstractVector{T}}) where T = op(α, β) -operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}, β::Union{T, AbstractVector{T}}, γ::Union{T, AbstractVector{T}}) where T = op(α, β, γ) +# Without `<:Number`, Julia v1.1.1 fails at precompilation with a StackOverflowError. +operate(op::Function, ::Type{T}, α::Union{T, AbstractVector{T}}...) where {T<:Number} = op(α...) """ operate!(op::Function, ::Type{T}, @@ -1790,7 +1790,9 @@ function vectorize(funcs::AbstractVector{MOI.ScalarQuadraticFunction{T}}) where return VQF(affine_terms, quadratic_terms, constant) end - +function promote_operation(::typeof(vcat), ::Type{T}, ::Type{T}...) where T + return Vector{T} +end function promote_operation(::typeof(vcat), ::Type{T}, ::Type{<:Union{ScalarAffineLike{T}, VVF, VAF{T}}}...) where T return VAF{T} diff --git a/test/Bridges/Constraint/soc_to_psd.jl b/test/Bridges/Constraint/soc_to_psd.jl index 55e8bbc40e..dbc655a715 100644 --- a/test/Bridges/Constraint/soc_to_psd.jl +++ b/test/Bridges/Constraint/soc_to_psd.jl @@ -8,7 +8,7 @@ const MOIB = MathOptInterface.Bridges include("../utilities.jl") -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) +mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) config = MOIT.TestConfig() @testset "SOCtoPSD" begin @@ -24,7 +24,16 @@ config = MOIT.TestConfig() (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) MOIT.soc1vtest(bridged_mock, config) MOIT.soc1ftest(bridged_mock, config) + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + value = [√2, -1.0, -1.0] + MOI.set(bridged_mock, attr, ci, value) + @test MOI.get(bridged_mock, attr, ci) ≈ value + end + test_delete_bridge(bridged_mock, ci, 3, ((MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0),)) end @@ -44,6 +53,15 @@ end mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2], (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[√2, -1/2, √2/8, -1/2, √2/8, √2/8]]) MOIT.rotatedsoc1ftest(bridged_mock, config) + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())) + + @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] + @test MOI.supports(bridged_mock, attr, typeof(ci)) + value = [√2, √2/4, -1.0, -1.0] + MOI.set(bridged_mock, attr, ci, value) + @test MOI.get(bridged_mock, attr, ci) ≈ value + end + test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0),)) end From 15e5e7ba1efe25d77a4f38054a687ca221ecd6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 29 Oct 2019 15:59:58 +0100 Subject: [PATCH 6/6] Add NormInfinity and NormOne to doc --- docs/src/apireference.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 39363aca74..85194fcee8 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -748,6 +748,8 @@ Bridges.Constraint.SplitIntervalBridge Bridges.Constraint.RSOCBridge Bridges.Constraint.SOCRBridge Bridges.Constraint.QuadtoSOCBridge +Bridges.Constraint.NormInfinityBridge +Bridges.Constraint.NormOneBridge Bridges.Constraint.GeoMeanBridge Bridges.Constraint.SquareBridge Bridges.Constraint.RootDetBridge