diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 1e9c0490c5..3e4da6601d 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -25,6 +25,7 @@ include("lazybridgeoptimizer.jl") # This is used by JuMP and removes the need to update JuMP everytime a bridge is added MOIU.@model AllBridgedConstraints () (Interval,) (SecondOrderCone, RotatedSecondOrderCone, GeometricMeanCone, LogDetConeTriangle, RootDetConeTriangle) () () (ScalarAffineFunction,) (VectorOfVariables,) (VectorAffineFunction,) + """ fullbridgeoptimizer(model::MOI.ModelLike, ::Type{T}) where T @@ -33,13 +34,14 @@ Returns a `LazyBridgeOptimizer` bridging `model` for every bridge defined in thi function fullbridgeoptimizer(model::MOI.ModelLike, ::Type{T}) where T bridgedmodel = MOIB.LazyBridgeOptimizer(model, AllBridgedConstraints{T}()) addbridge!(bridgedmodel, MOIB.SplitIntervalBridge{T}) + addbridge!(bridgedmodel, MOIB.RSOCBridge{T}) + addbridge!(bridgedmodel, MOIB.SOCtoQuadBridge{T}) addbridge!(bridgedmodel, MOIB.GeoMeanBridge{T}) addbridge!(bridgedmodel, MOIB.SquarePSDBridge{T}) addbridge!(bridgedmodel, MOIB.LogDetBridge{T}) addbridge!(bridgedmodel, MOIB.RootDetBridge{T}) - addbridge!(bridgedmodel, MOIB.RSOCBridge{T}) - addbridge!(bridgedmodel, MOIB.RSOCtoPSDCBridge{T}) addbridge!(bridgedmodel, MOIB.SOCtoPSDCBridge{T}) + addbridge!(bridgedmodel, MOIB.RSOCtoPSDCBridge{T}) bridgedmodel end @@ -47,6 +49,8 @@ include("intervalbridge.jl") @bridge SplitInterval SplitIntervalBridge () (Interval,) () () (SingleVariable,) (ScalarAffineFunction, ScalarQuadraticFunction) () () include("rsocbridge.jl") @bridge RSOC RSOCBridge () () (RotatedSecondOrderCone,) () () () (VectorOfVariables,) (VectorAffineFunction,) +include("soctoquadbridge.jl") +@bridge SOCtoQuad SOCtoQuadBridge () () (SecondOrderCone,) () () () (VectorOfVariables,) (VectorAffineFunction,) include("geomeanbridge.jl") @bridge GeoMean GeoMeanBridge () () (GeometricMeanCone,) () () () (VectorOfVariables,) (VectorAffineFunction,) include("squarepsdbridge.jl") diff --git a/src/Bridges/bridge.jl b/src/Bridges/bridge.jl index 75b237b86f..ff9e389bf0 100644 --- a/src/Bridges/bridge.jl +++ b/src/Bridges/bridge.jl @@ -76,3 +76,11 @@ function concrete_bridge_type(bridge_type::DataType, ::Type{<:MOI.AbstractSet}) return bridge_type end + +""" + need_constraint_primal_fallback(BT::Type{<:AbstractBridge}) + +Return a `Bool` indicating whether bridges of concrete type `BT` need the bridge +optimizer to use `get_fallback`. +""" +need_constraint_primal_fallback(::Type{<:AbstractBridge}) = false diff --git a/src/Bridges/bridgeoptimizer.jl b/src/Bridges/bridgeoptimizer.jl index 25e882dfaa..62c0271f4b 100644 --- a/src/Bridges/bridgeoptimizer.jl +++ b/src/Bridges/bridgeoptimizer.jl @@ -208,10 +208,14 @@ end # Constraint attributes function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.AbstractConstraintAttribute, - ci::CI) + ci::CI{F, S}) where {F, S} if isbridged(b, typeof(ci)) if MOIU.is_result_attribute(attr) - MOI.get(b, attr, bridge(b, ci)) + if attr isa MOI.ConstraintPrimal && need_constraint_primal_fallback(concrete_bridge_type(b, F, S)) + MOIU.get_fallback(b, attr, ci) + else + MOI.get(b, attr, bridge(b, ci)) + end else MOI.get(b.bridged, attr, ci) end diff --git a/src/Bridges/soctoquadbridge.jl b/src/Bridges/soctoquadbridge.jl new file mode 100644 index 0000000000..21a6ff4cc6 --- /dev/null +++ b/src/Bridges/soctoquadbridge.jl @@ -0,0 +1,83 @@ +""" + SOCtoQuadBridge{T} + +Constraints of the form `VectorOfVariables`-in-`SecondOrderCone` (resp. +`VectorAffineFunction`-in-`SecondOrderCone`) can be transformed into a +`ScalarQuadraticFunction`-in-`GreaterThan` and a +`SingleVariable`-in-`GreaterThan` (resp. +`ScalarAffineFunction`-in-`SecondOrderCone`). Indeed, the definition of the +second-order cone +```math +t \\ge || x ||_2 \\} +``` +is equivalent to +```math +\\sum x_i^2 \\le t^2 +``` +with ``t \\ge 0``. +""" +struct SOCtoQuadBridge{T, F, G} <: AbstractBridge + quad::CI{F, MOI.GreaterThan{T}} + t_nonneg::CI{G, MOI.GreaterThan{T}} +end +function SOCtoQuadBridge{T, F, G}(model::MOI.ModelLike, + f::MOI.AbstractVectorFunction, + s::MOI.SecondOrderCone) where {T, F, G} + d = s.dimension + f_scalars = MOIU.eachscalar(f) + t = f_scalars[1] + t_nonneg = MOIU.add_scalar_constraint(model, t, MOI.GreaterThan(zero(T))) + quad_f = MOIU.operate(*, T, t, t) + for i in 2:d + x = f_scalars[i] + quad_f = MOIU.operate!(-, T, quad_f, MOIU.operate(*, T, x, x)) + end + quad = MOIU.add_scalar_constraint(model, quad_f, MOI.GreaterThan(zero(T))) + return SOCtoQuadBridge{T, F, G}(quad, t_nonneg) +end + +function MOI.supportsconstraint(::Type{SOCtoQuadBridge{T}}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.SecondOrderCone}) where T + return true +end +function addedconstrainttypes(::Type{SOCtoQuadBridge{T, F, G}}) where {T, F, G} + list = [(F, MOI.GreaterThan{T})] + if F != G + push!(list, (G, MOI.GreaterThan{T})) + end + return list +end +function concrete_bridge_type(::Type{<:SOCtoQuadBridge{T}}, + H::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.SecondOrderCone}) where T + G = MOIU.scalar_type(H) + Q = MOIU.promote_operation(*, T, G, G) + F = MOIU.promote_operation(-, T, Q, Q) + return SOCtoQuadBridge{T, F, G} +end + +# Attributes, Bridge acting as an model +function MOI.get(b::SOCtoQuadBridge{T, F, G}, + ::MOI.NumberOfConstraints{H, MOI.GreaterThan{T}}) where {T, F, G, H} + return (F == H) + (G == H) +end +function MOI.get(b::SOCtoQuadBridge{T, F, G}, + ::MOI.ListOfConstraintIndices{H, MOI.GreaterThan{T}}) where {T, F, G, H} + list = CI{H, MOI.GreaterThan{T}}[] + if F == H + push!(list, b.quad) + end + if G == H + push!(list, b.t_nonneg) + end + return list +end + +# References +function MOI.delete!(model::MOI.ModelLike, bridge::SOCtoQuadBridge) + MOI.delete!(model, bridge.t_nonneg) + MOI.delete!(model, bridge.quad) +end + +need_constraint_primal_fallback(::Type{<:SOCtoQuadBridge}) = true diff --git a/test/bridge.jl b/test/bridge.jl index 593bf8e871..8cfd5d7a76 100644 --- a/test/bridge.jl +++ b/test/bridge.jl @@ -212,6 +212,34 @@ end test_delete_bridge(bridgedmock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone, 0),)) end + @testset "SOCtoQuad" begin + config = MOIT.TestConfig(duals=false) + bridgedmock = MOIB.SOCtoQuad{Float64}(mock) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2]) + MOIT.soc1vtest(bridgedmock, config) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2]) + MOIT.soc1ftest(bridgedmock, config) + ci = first(MOI.get(bridgedmock, + MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone}())) + test_delete_bridge(bridgedmock, ci, 3, + ((MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, 0),)) + end + + @testset "GeoMean" begin + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [ones(4); 2; √2; √2]) + bridgedmock = MOIB.GeoMean{Float64}(mock) + MOIT.geomean1vtest(bridgedmock, config) + MOIT.geomean1ftest(bridgedmock, config) + # Dual is not yet implemented for GeoMean bridge + ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}())) + @test !MOI.supports(bridgedmock, MOI.ConstraintSet(), typeof(ci)) + @test !MOI.supports(bridgedmock, MOI.ConstraintFunction(), typeof(ci)) + test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 1))) + end + @testset "SquarePSD" begin bridgedmock = MOIB.SquarePSD{Float64}(mock) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), @@ -231,19 +259,31 @@ end MOI.EqualTo{Float64}, 1))) end - @testset "GeoMean" begin - mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [ones(4); 2; √2; √2]) - bridgedmock = MOIB.GeoMean{Float64}(mock) - MOIT.geomean1vtest(bridgedmock, config) - MOIT.geomean1ftest(bridgedmock, config) - # Dual is not yet implemented for GeoMean bridge - ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}())) + @testset "LogDet" begin + bridgedmock = MOIB.LogDet{Float64}(mock) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1, 0, 1, 1, 0, 1, 0, 0]) + MOIT.logdett1vtest(bridgedmock, config) + MOIT.logdett1ftest(bridgedmock, config) + # Dual is not yet implemented for LogDet bridge + ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle}())) @test !MOI.supports(bridgedmock, MOI.ConstraintSet(), typeof(ci)) @test !MOI.supports(bridgedmock, MOI.ConstraintFunction(), typeof(ci)) - test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0), - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 1))) + test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone, 0), (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0))) end + @testset "RootDet" begin + bridgedmock = MOIB.RootDet{Float64}(mock) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 0, 1, 1, 0, 1]) + MOIT.rootdett1vtest(bridgedmock, config) + MOIT.rootdett1ftest(bridgedmock, config) + # Dual is not yet implemented for RootDet bridge + ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle}())) + @test !MOI.supports(bridgedmock, MOI.ConstraintSet(), typeof(ci)) + @test !MOI.supports(bridgedmock, MOI.ConstraintFunction(), typeof(ci)) + test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0), + (MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, 0), + (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0))) + end @testset "SOCtoPSD" begin bridgedmock = MOIB.SOCtoPSD{Float64}(mock) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], @@ -272,29 +312,4 @@ end test_delete_bridge(bridgedmock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0),)) end - @testset "LogDet" begin - bridgedmock = MOIB.LogDet{Float64}(mock) - mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1, 0, 1, 1, 0, 1, 0, 0]) - MOIT.logdett1vtest(bridgedmock, config) - MOIT.logdett1ftest(bridgedmock, config) - # Dual is not yet implemented for LogDet bridge - ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle}())) - @test !MOI.supports(bridgedmock, MOI.ConstraintSet(), typeof(ci)) - @test !MOI.supports(bridgedmock, MOI.ConstraintFunction(), typeof(ci)) - test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone, 0), (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0))) - end - - @testset "RootDet" begin - bridgedmock = MOIB.RootDet{Float64}(mock) - mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 0, 1, 1, 0, 1]) - MOIT.rootdett1vtest(bridgedmock, config) - MOIT.rootdett1ftest(bridgedmock, config) - # Dual is not yet implemented for RootDet bridge - ci = first(MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle}())) - @test !MOI.supports(bridgedmock, MOI.ConstraintSet(), typeof(ci)) - @test !MOI.supports(bridgedmock, MOI.ConstraintFunction(), typeof(ci)) - test_delete_bridge(bridgedmock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0), - (MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, 0), - (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0))) - end end