Skip to content

Add Bridges.ListOfNonstandardBridges attribute #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/src/submodules/Bridges/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Bridges.add_bridge
Bridges.remove_bridge
Bridges.has_bridge
Bridges.full_bridge_optimizer
Bridges.ListOfNonstandardBridges
Bridges.debug_supports_constraint
Bridges.debug_supports
Bridges.bridged_variable_function
Expand Down Expand Up @@ -59,9 +60,10 @@ Bridges.inverse_adjoint_map_function

```@docs
Bridges.Constraint.FlipSignBridge
Bridges.Constraint.AbstractToIntervalBridge
Bridges.Constraint.GreaterToIntervalBridge
Bridges.Constraint.GreaterToLessBridge
Bridges.Constraint.LessToIntervalBridge
Bridges.Constraint.GreaterToLessBridge
Bridges.Constraint.LessToGreaterBridge
Bridges.Constraint.NonnegToNonposBridge
Bridges.Constraint.NonposToNonnegBridge
Expand Down
102 changes: 100 additions & 2 deletions src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,115 @@ include("debug.jl")
"""
full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where {T}

Returns a `LazyBridgeOptimizer` bridging `model` for every bridge defined in
this package and for the coefficient type `T`.
Returns a [`LazyBridgeOptimizer`](@ref) bridging `model` for every bridge
defined in this package (see below for the few exceptions) and for the
coefficient type `T` in addition to the bridges in the list returned by
`MOI.get(model, MOI.Bridges.ListOfNonstandardBridges{T}())`.

See also [`ListOfNonstandardBridges`](@ref).

!!! note
The following bridges are not added by `full_bridge_optimizer` except if
they are in the list returned by `MOI.get(model, MOI.Bridges.ListOfNonstandardBridges{T}())`
(see the docstrings of the corresponding bridge for the reason they are not
added):
* [`Constraint.SOCtoNonConvexQuadBridge`](@ref),
[`Constraint.RSOCtoNonConvexQuadBridge`](@ref) and
[`Constraint.SOCtoPSDBridge`](@ref).
* The subtypes of [`Constraint.AbstractToIntervalBridge`](@ref) (i.e.
[`Constraint.GreaterToIntervalBridge`](@ref) and
[`Constraint.LessToIntervalBridge`](@ref)) if `T` is not a subtype of
`AbstractFloat`.
"""
function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where {T}
bridged_model = LazyBridgeOptimizer(model)
for BT in MOI.get(model, ListOfNonstandardBridges{T}())
add_bridge(bridged_model, BT)
end
Variable.add_all_bridges(bridged_model, T)
Constraint.add_all_bridges(bridged_model, T)
Objective.add_all_bridges(bridged_model, T)
return bridged_model
end

"""
ListOfNonstandardBridges{T}() <: MOI.AbstractOptimizerAttribute

Any optimizer can be wrapped in a [`LazyBridgeOptimizer`](@ref) using
[`full_bridge_optimizer`](@ref). However, by default [`LazyBridgeOptimizer`](@ref)
uses a limited set of bridges that are:

1. implemented in `MOI.Bridges`
2. generally applicable for all optimizers.

For some optimizers however, it is useful to add additional bridges, such as
those that are implemented in external packages (e.g., within the solver package
itself) or only apply in certain circumstances (e.g.,
[`Constraint.SOCtoNonConvexQuadBridge`](@ref)).

Such optimizers should implement the `ListOfNonstandardBridges` attribute to
return a vector of bridge types that are added by [`full_bridge_optimizer`](@ref)
in addition to the list of default bridges.

Note that optimizers implementing `ListOfNonstandardBridges` may require
package-specific functions or sets to be used if the non-standard bridges
are not added. Therefore, you are recommended to use
`model = MOI.instantiate(Package.Optimizer; with_bridge_type = T)` instead of
`model = MOI.instantiate(Package.Optimizer)`. See
[`MathOptInterface.instantiate`](@ref).

## Examples

### An optimizer using a non-default bridge in `MOI.Bridges`

Solvers supporting [`MOI.ScalarQuadraticFunction`](@ref) can support
[`MOI.SecondOrderCone`](@ref) and [`MOI.RotatedSecondOrderCone`](@ref) by
defining:
```julia
function MOI.get(::MyQuadraticOptimizer, ::ListOfNonstandardBridges{Float64})
return Type[
MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge{Float64},
MOI.Bridges.Constraint.RSOCtoNonConvexQuadBridge{Float64},
]
end
```

### An optimizer defining an internal bridge

Suppose an optimizer can exploit specific structure of a constraint, e.g., it
can exploit the structure of the matrix `A` in the linear system of equations
`A * x = b`.

The optimizer can define the function:
```julia
struct MatrixAffineFunction{T} <: MOI.AbstractVectorFunction
A::SomeStructuredMatrixType{T}
b::Vector{T}
end
```
and then a bridge
```julia
struct MatrixAffineFunctionBridge{T} <: MOI.Constraint.AbstractBridge
# ...
end
# ...
```
from `VectorAffineFunction{T}` to the `MatrixAffineFunction`. Finally, it
defines:
```julia
function MOI.get(::Optimizer{T}, ::ListOfNonstandardBridges{T}) where {T}
return Type[MatrixAffineFunctionBridge{T}]
end
```
"""
struct ListOfNonstandardBridges{T} <: MOI.AbstractOptimizerAttribute end

attribute_value_type(::ListOfNonstandardBridges) = Vector{Type}

MOI.is_copyable(::ListOfNonstandardBridges) = false

MOI.get_fallback(model::MOI.ModelLike, ::ListOfNonstandardBridges) = Type[]

print_num_bridges(io::IO, ::Variable.EmptyMap) = nothing

print_num_bridges(io::IO, ::Constraint.EmptyMap) = nothing
Expand Down
12 changes: 9 additions & 3 deletions src/Bridges/Constraint/ltgt_to_interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ Bridge a `F`-in-`Interval` constraint into an `F`-in-`Interval{T}` constraint wh

The `F`-in-`Interval{T}` constraint is stored in the `constraint`
field by convention.
It is required that T be a AbstractFloat type because otherwise
typemin and typemax would either be not implemented (e.g. BigInt)
or would not give infinite value (e.g. Int).

!!! warning
It is required that `T` be a `AbstractFloat` type because otherwise
typemin and typemax would either be not implemented (e.g. BigInt)
or would not give infinite value (e.g. Int). For this reason,
this bridge is only added to
[`MathOptInterface.Bridges.full_bridge_optimizer`](@ref).

when `T` is a subtype of `AbstractFloat`.
"""
abstract type AbstractToIntervalBridge{
T<:AbstractFloat,
Expand Down
15 changes: 8 additions & 7 deletions src/Bridges/Constraint/soc_to_nonconvex_quad.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ is equivalent to
```
with ``t \\ge 0``. (3)

*WARNING* This transformation starts from a convex constraint (1) and creates a
non-convex constraint (2), because the Q matrix associated with the constraint 2
has one negative eigenvalue. This might be wrongly interpreted by a solver.
Some solvers can look at (2) and understand that it is a second order cone, but
this is not a general rule.
For these reasons this bridge is not automatically added by [`MOI.Bridges.full_bridge_optimizer`](@ref).
Care is recommended when adding this bridge to a optimizer.
!!! warning
This transformation starts from a convex constraint (1) and creates a
non-convex constraint (2), because the Q matrix associated with the constraint (2)
has one negative eigenvalue. This might be wrongly interpreted by a solver.
Some solvers can look at (2) and understand that it is a second order cone, but
this is not a general rule.
For these reasons this bridge is not automatically added by [`MOI.Bridges.full_bridge_optimizer`](@ref).
Care is recommended when adding this bridge to a optimizer.
"""
struct SOCtoNonConvexQuadBridge{T} <: AbstractSOCtoNonConvexQuadBridge{T}
quad::CI{MOI.ScalarQuadraticFunction{T},MOI.LessThan{T}}
Expand Down
10 changes: 6 additions & 4 deletions src/Bridges/Constraint/soc_to_psd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ which is equivalent to
t^2 & > x^\\top x
\\end{align*}
```
This bridge is not added by default by [`MOI.Bridges.full_bridge_optimizer`](@ref)
as bridging second order cone constraints to semidefinite constraints can be
achieved by the [`SOCtoRSOCBridge`](@ref) followed by the [`RSOCtoPSDBridge`](@ref)
while creating a smaller semidefinite constraint.

!!! warning
This bridge is not added by default by [`MOI.Bridges.full_bridge_optimizer`](@ref)
as bridging second order cone constraints to semidefinite constraints can be
achieved by the [`SOCtoRSOCBridge`](@ref) followed by the [`RSOCtoPSDBridge`](@ref)
while creating a smaller semidefinite constraint.
"""
struct SOCtoPSDBridge{T,F,G} <: SetMapBridge{
T,
Expand Down
19 changes: 19 additions & 0 deletions test/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,25 @@ function test_wrong_coefficient()
return
end

struct OptimizerWithBridgeListOfNonstandardBridges <: MOI.AbstractOptimizer end
struct BridgeListOfNonstandardBridges{T} <:
MOI.Bridges.Constraint.AbstractBridge end
function MOI.get(
::OptimizerWithBridgeListOfNonstandardBridges,
::MOI.Bridges.ListOfNonstandardBridges{T},
) where {T}
return [BridgeListOfNonstandardBridges{T}]
end

function test_toadd()
b = MOI.Bridges.full_bridge_optimizer(
OptimizerWithBridgeListOfNonstandardBridges(),
Int,
)
@test MOI.Bridges.has_bridge(b, BridgeListOfNonstandardBridges{Int})
@test !MOI.Bridges.has_bridge(b, BridgeListOfNonstandardBridges{Float64})
end

end # module

TestBridgesLazyBridgeOptimizer.runtests()