Skip to content

Commit

Permalink
[Bridges.Constraint] add bridges in alphabetical order (#2596)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Jan 5, 2025
1 parent 461006f commit 33ec305
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 68 deletions.
155 changes: 92 additions & 63 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,84 +29,113 @@ Add all bridges defined in the `Bridges.Constraint` submodule to `model`.
The coefficient type used is `T`.
"""
function add_all_bridges(model, ::Type{T}) where {T}
if T <: AbstractFloat
MOI.Bridges.add_bridge(model, AllDifferentToCountDistinctBridge{T})
MOI.Bridges.add_bridge(model, BinPackingToMILPBridge{T})
MOI.Bridges.add_bridge(model, CircuitToMILPBridge{T})
MOI.Bridges.add_bridge(model, ComplexNormInfinityToSecondOrderConeBridge{T})
MOI.Bridges.add_bridge(model, CountAtLeastToCountBelongsBridge{T})
MOI.Bridges.add_bridge(model, CountBelongsToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountDistinctToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountGreaterThanToMILPBridge{T})
# * ExponentialConeToScalarNonlinearFunctionBridge{T}
# This bridge is not added by default because it starts with a convex
# conic constraint and adds a nonlinear constraint that local NLP
# solvers like Ipopt can struggle with because of log(x) when x is 0.
# In addition, the bridge does not support ConstraintDual.
# * FunctionConversionBridge{T}
# This bridge is not added because, even though it is not abstract, it
# is highly parameterized, and parameterized versions such as
# ScalarFunctionizeBridge are added.
MOI.Bridges.add_bridge(model, GeoMeanBridge{T})
MOI.Bridges.add_bridge(model, GeoMeanToPowerBridge{T})
MOI.Bridges.add_bridge(model, GeoMeantoRelEntrBridge{T})
if T <: AbstractFloat # See note in docstring of AbstractToIntervalBridge
MOI.Bridges.add_bridge(model, GreaterToIntervalBridge{T})
MOI.Bridges.add_bridge(model, LessToIntervalBridge{T})
end
MOI.Bridges.add_bridge(model, GreaterToLessBridge{T})
MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T})
MOI.Bridges.add_bridge(model, IndicatorActiveOnFalseBridge{T})
MOI.Bridges.add_bridge(model, IndicatorGreaterToLessThanBridge{T})
MOI.Bridges.add_bridge(model, IndicatorLessToGreaterThanBridge{T})
# * IndicatorSetMapBridge{T}
# This bridge is not added because, even though it is not abstract, it
# is highly parameterized, and parameterized versions such as
# IndicatorGreaterToLessThanBridge are added.
MOI.Bridges.add_bridge(model, IndicatorSOS1Bridge{T})
MOI.Bridges.add_bridge(model, IndicatorToMILPBridge{T})
# * InequalityToComplementsBridge{T}
# This bridge is not added because of a bug in Convex.jl:
# https://github.com/jump-dev/Convex.jl/blob/ca5324217575af263bfeee20b3e0526bed051887/src/MOI_wrapper.jl#L119-L133
# It is also really useful only to PATHSolver.jl, which could add this
# to MOI.ListOfRequiredBridges.
MOI.Bridges.add_bridge(model, IntegerToZeroOneBridge{T})
MOI.Bridges.add_bridge(model, LessToGreaterBridge{T})
if T <: AbstractFloat # See note in docstring of AbstractToIntervalBridge
MOI.Bridges.add_bridge(model, LessToIntervalBridge{T})
end
MOI.Bridges.add_bridge(model, LogDetBridge{T})
MOI.Bridges.add_bridge(model, NonnegToNonposBridge{T})
MOI.Bridges.add_bridge(model, NonposToNonnegBridge{T})
MOI.Bridges.add_bridge(model, ScalarizeBridge{T})
MOI.Bridges.add_bridge(model, VectorizeBridge{T})
MOI.Bridges.add_bridge(model, ScalarSlackBridge{T})
MOI.Bridges.add_bridge(model, VectorSlackBridge{T})
MOI.Bridges.add_bridge(model, ScalarFunctionizeBridge{T})
MOI.Bridges.add_bridge(model, VectorFunctionizeBridge{T})
MOI.Bridges.add_bridge(model, ToScalarQuadraticBridge{T})
MOI.Bridges.add_bridge(model, ToVectorQuadraticBridge{T})
MOI.Bridges.add_bridge(model, ToScalarNonlinearBridge{T})
MOI.Bridges.add_bridge(model, SplitHyperRectangleBridge{T})
MOI.Bridges.add_bridge(model, SplitIntervalBridge{T})
MOI.Bridges.add_bridge(model, SplitComplexEqualToBridge{T})
MOI.Bridges.add_bridge(model, SplitComplexZerosBridge{T})
MOI.Bridges.add_bridge(model, QuadtoSOCBridge{T})
# We do not add `(R)SOCtoNonConvexQuad` because it starts with a convex
# conic constraint and generate a non-convex constraint (in the QCP
# interpretation).
MOI.Bridges.add_bridge(model, NormInfinityBridge{T})
MOI.Bridges.add_bridge(model, NormInfinityConeToNormConeBridge{T})
MOI.Bridges.add_bridge(model, NormNuclearBridge{T})
MOI.Bridges.add_bridge(model, NormOneBridge{T})
MOI.Bridges.add_bridge(model, GeoMeantoRelEntrBridge{T})
MOI.Bridges.add_bridge(model, GeoMeanBridge{T})
MOI.Bridges.add_bridge(model, GeoMeanToPowerBridge{T})
MOI.Bridges.add_bridge(model, NormToPowerBridge{T})
MOI.Bridges.add_bridge(model, NormOneConeToNormConeBridge{T})
MOI.Bridges.add_bridge(model, SecondOrderConeToNormConeBridge{T})
MOI.Bridges.add_bridge(model, NormInfinityConeToNormConeBridge{T})
MOI.Bridges.add_bridge(model, ComplexNormInfinityToSecondOrderConeBridge{T})
MOI.Bridges.add_bridge(model, RelativeEntropyBridge{T})
# * NormSpecialCaseBridge{T}
# This bridge is not added because, even though it is not abstract, it
# is highly parameterized, and parameterized versions such as
# NormOneConeToNormConeBridge are added.
MOI.Bridges.add_bridge(model, NormSpectralBridge{T})
MOI.Bridges.add_bridge(model, NormNuclearBridge{T})
MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T})
MOI.Bridges.add_bridge(model, SquareBridge{T})
MOI.Bridges.add_bridge(model, SetDotScalingBridge{T})
MOI.Bridges.add_bridge(model, SetDotInverseScalingBridge{T})
MOI.Bridges.add_bridge(model, LogDetBridge{T})
MOI.Bridges.add_bridge(model, NormToPowerBridge{T})
# * NumberConversionBridge{T}
# This bridge is not added by default because it would silently enable
# models with mixed precision. In most cases, this is a bug in the
# user's code, so we leave this bridge as opt-in.
MOI.Bridges.add_bridge(model, QuadtoSOCBridge{T})
MOI.Bridges.add_bridge(model, ReifiedAllDifferentToCountDistinctBridge{T})
MOI.Bridges.add_bridge(model, ReifiedCountDistinctToMILPBridge{T})
MOI.Bridges.add_bridge(model, RelativeEntropyBridge{T})
MOI.Bridges.add_bridge(model, RootDetBridge{T})
MOI.Bridges.add_bridge(model, RSOCtoSOCBridge{T})
MOI.Bridges.add_bridge(model, SOCtoRSOCBridge{T})
# We do not add `SOCtoPSDBridge` as transforming the `SOC` to `RSOC` and
# then to `PSD` produces a smaller SDP constraint.
# MOI.Bridges.add_bridge(model, SOCtoPSDBridge{T})
# * RSOCtoNonConvexQuadBridge{T}
# This bridge is not added by default because it starts with a convex
# conic constraint and generate a non-convex constraint (in the QCP
# interpretation).
MOI.Bridges.add_bridge(model, RSOCtoPSDBridge{T})
MOI.Bridges.add_bridge(model, IndicatorActiveOnFalseBridge{T})
MOI.Bridges.add_bridge(model, IndicatorSOS1Bridge{T})
MOI.Bridges.add_bridge(model, IndicatorLessToGreaterThanBridge{T})
MOI.Bridges.add_bridge(model, IndicatorGreaterToLessThanBridge{T})
MOI.Bridges.add_bridge(model, RSOCtoSOCBridge{T})
MOI.Bridges.add_bridge(model, ScalarFunctionizeBridge{T})
MOI.Bridges.add_bridge(model, ScalarizeBridge{T})
MOI.Bridges.add_bridge(model, ScalarSlackBridge{T})
MOI.Bridges.add_bridge(model, SecondOrderConeToNormConeBridge{T})
MOI.Bridges.add_bridge(model, SemiToBinaryBridge{T})
MOI.Bridges.add_bridge(model, ZeroOneBridge{T})
MOI.Bridges.add_bridge(model, IntegerToZeroOneBridge{T})
MOI.Bridges.add_bridge(model, InequalityToComplementsBridge{T})
# Do not add by default
# MOI.Bridges.add_bridge(model, NumberConversionBridge{T})
# Constraint programming bridges
MOI.Bridges.add_bridge(model, AllDifferentToCountDistinctBridge{T})
MOI.Bridges.add_bridge(model, ReifiedAllDifferentToCountDistinctBridge{T})
MOI.Bridges.add_bridge(model, BinPackingToMILPBridge{T})
MOI.Bridges.add_bridge(model, CircuitToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountAtLeastToCountBelongsBridge{T})
MOI.Bridges.add_bridge(model, CountBelongsToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountDistinctToMILPBridge{T})
MOI.Bridges.add_bridge(model, ReifiedCountDistinctToMILPBridge{T})
MOI.Bridges.add_bridge(model, CountGreaterThanToMILPBridge{T})
MOI.Bridges.add_bridge(model, TableToMILPBridge{T})
# * SetConversionBridge{T}
# This bridge is not added because, even though it is not abstract, it
# is highly parameterized, and it intended for use by MOI extensions.
MOI.Bridges.add_bridge(model, SetDotInverseScalingBridge{T})
MOI.Bridges.add_bridge(model, SetDotScalingBridge{T})
# * SOCtoNonConvexQuadBridge{T}
# This bridge is not added by default because it starts with a convex
# conic constraint and generate a non-convex constraint (in the QCP
# interpretation).
# * SOCtoPSDBridge{T}
# This bridge is not added because transforming the `SOC` to `RSOC` and
# then to `PSD` produces a smaller SDP constraint. `RSOCtoPSDBridge` is
# added by default.
MOI.Bridges.add_bridge(model, SOCtoRSOCBridge{T})
MOI.Bridges.add_bridge(model, SOS1ToMILPBridge{T})
MOI.Bridges.add_bridge(model, SOS2ToMILPBridge{T})
MOI.Bridges.add_bridge(model, IndicatorToMILPBridge{T})
MOI.Bridges.add_bridge(
model,
ExponentialConeToScalarNonlinearFunctionBridge{T},
)
MOI.Bridges.add_bridge(model, SplitComplexEqualToBridge{T})
MOI.Bridges.add_bridge(model, SplitComplexZerosBridge{T})
MOI.Bridges.add_bridge(model, SplitHyperRectangleBridge{T})
MOI.Bridges.add_bridge(model, SplitIntervalBridge{T})
MOI.Bridges.add_bridge(model, SquareBridge{T})
MOI.Bridges.add_bridge(model, TableToMILPBridge{T})
MOI.Bridges.add_bridge(model, ToScalarNonlinearBridge{T})
MOI.Bridges.add_bridge(model, ToScalarQuadraticBridge{T})
MOI.Bridges.add_bridge(model, ToVectorQuadraticBridge{T})
MOI.Bridges.add_bridge(model, VectorFunctionizeBridge{T})
MOI.Bridges.add_bridge(model, VectorizeBridge{T})
MOI.Bridges.add_bridge(model, VectorSlackBridge{T})
MOI.Bridges.add_bridge(model, ZeroOneBridge{T})
return
end

Expand Down
10 changes: 10 additions & 0 deletions src/Bridges/Constraint/bridges/SOCtoPSDBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ end
const SOCtoPSD{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{SOCtoPSDBridge{T},OT}

# This bridge destorys a lot of structure and adding PSD variables is almost
# always undesirable. We give this bridge a high cost so that it is used only if
# necessary.
MOI.Bridges.bridging_cost(::Type{<:SOCtoPSDBridge}) = 10.0

function concrete_bridge_type(
::Type{<:SOCtoPSDBridge{T}},
G::Type{<:MOI.AbstractVectorFunction},
Expand Down Expand Up @@ -186,6 +191,11 @@ end
const RSOCtoPSD{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{RSOCtoPSDBridge{T},OT}

# This bridge destorys a lot of structure and adding PSD variables is almost
# always undesirable. We give this bridge a high cost so that it is used only if
# necessary.
MOI.Bridges.bridging_cost(::Type{<:RSOCtoPSDBridge}) = 10.0

function concrete_bridge_type(
::Type{<:RSOCtoPSDBridge{T}},
G::Type{<:MOI.AbstractVectorFunction},
Expand Down
4 changes: 2 additions & 2 deletions src/Bridges/Variable/bridges/RSOCtoPSDBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ const RSOCtoPSD{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{RSOCtoPSDBridge{T},OT}

# This bridge destorys a lot of structure and adding PSD variables is almost
# always undesirable. We give this bridge an arbitrarily hight cost so that it
# is used only if necessary.
# always undesirable. We give this bridge a high cost so that it is used only if
# necessary.
MOI.Bridges.bridging_cost(::Type{<:RSOCtoPSDBridge}) = 10.0

function bridge_constrained_variable(
Expand Down
20 changes: 20 additions & 0 deletions test/Bridges/Constraint/SOCtoPSDBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,26 @@ function test_runtests()
return
end

function test_bridging_cost_SOCtoPSD()
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
model = MOI.Bridges.Constraint.SOCtoPSD{Float64}(inner)
x = MOI.add_variables(model, 3)
c = MOI.add_constraint(model, x, MOI.SecondOrderCone(3))
bridge = model.map[c]
MOI.Bridges.bridging_cost(typeof(bridge)) == 10.0
return
end

function test_bridging_cost_RSOCtoPSD()
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
model = MOI.Bridges.Constraint.RSOCtoPSD{Float64}(inner)
x = MOI.add_variables(model, 3)
c = MOI.add_constraint(model, x, MOI.RotatedSecondOrderCone(3))
bridge = model.map[c]
MOI.Bridges.bridging_cost(typeof(bridge)) == 10.0
return
end

end # module

TestConstraintSOCtoPSD.runtests()
14 changes: 14 additions & 0 deletions test/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,20 @@ function test_double_deletion_scalar()
# careful not to delete the second one twice, see https://github.com/jump-dev/MathOptInterface.jl/issues/1231
model =
MOI.instantiate(AffineOnlyModel{Float64}, with_bridge_type = Float64)
# If LessToGreaterBridge exists, this goes from:
# VariableIndex(x) in LessThan(1.0)
# ScalarAffineFunction(-1.0x) in GreaterThan(-1.0) (LessToGreater)
# ScalarAffineFunction(-1.0x) in Interval(-1.0, Inf) (GreaterToInterval)
# we want instead
# VariableIndex(x) in LessThan(1.0)
# VariableIndex(x) in Interval(-Inf, 1.0) (LessToInterval)
# ScalarAffineFunction(1.0x) in Interval(-Inf, 1.0) (FunctionConversion)
# To check that we handle the LessThan and Interval sets on the same
# VariableIndex correctly.
MOI.Bridges.remove_bridge(
model,
MOI.Bridges.Constraint.LessToGreaterBridge{Float64},
)
x = MOI.add_variable(model)
c = MOI.add_constraint(model, x, MOI.LessThan(1.0))
# Need to test the bridging to make sure it's not functionized first as otherwise,
Expand Down
5 changes: 2 additions & 3 deletions test/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,6 @@ function test_MOI_runtests_No_RSOCModel()
return
end

# Test that RSOCtoPSD is used instead of RSOC+SOCtoPSD as it is a shortest path.
function test_bridge_selection()
mock = MOI.Utilities.MockOptimizer(NoRSOCModel{Float64}())
bridged_mock = MOI.Bridges.LazyBridgeOptimizer(mock)
Expand Down Expand Up @@ -1751,13 +1750,13 @@ function test_bridge_selection()
bridged_mock,
MOI.VectorOfVariables,
MOI.RotatedSecondOrderCone,
) == MOI.Bridges.Constraint.RSOCtoPSDBridge{
) == MOI.Bridges.Constraint.RSOCtoSOCBridge{
Float64,
MOI.VectorAffineFunction{Float64},
MOI.VectorOfVariables,
}
@test MOI.Bridges.bridge(bridged_mock, c) isa
MOI.Bridges.Constraint.RSOCtoPSDBridge
MOI.Bridges.Constraint.RSOCtoSOCBridge
@test bridged_mock.graph.constraint_dist[MOI.Bridges.node(
bridged_mock,
MOI.VectorOfVariables,
Expand Down

0 comments on commit 33ec305

Please sign in to comment.