Skip to content
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
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ makedocs(
],
"Bridges" => [
"Overview" => "submodules/Bridges/overview.md",
"Implementation" => "submodules/Bridges/implementation.md",
"API Reference" => "submodules/Bridges/reference.md",
],
"FileFormats" => [
Expand Down
47 changes: 47 additions & 0 deletions docs/src/submodules/Bridges/implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
```@meta
CurrentModule = MathOptInterface
DocTestSetup = quote
using MathOptInterface
const MOI = MathOptInterface
end
DocTestFilters = [r"MathOptInterface|MOI"]
```

# Bridge interface

A bridge should implement the following functions to be usable by a bridge optimizer:
```@docs
Bridges.added_constrained_variable_types
Bridges.added_constraint_types
```
Additionally, variable bridges should implement:
```@docs
Bridges.Variable.supports_constrained_variable
Bridges.Variable.concrete_bridge_type
Bridges.Variable.bridge_constrained_variable
```
constraint bridges should implement:
```@docs
supports_constraint(::Type{<:Bridges.Constraint.AbstractBridge}, ::Type{<:AbstractFunction}, ::Type{<:AbstractSet})
Bridges.Constraint.concrete_bridge_type
Bridges.Constraint.bridge_constraint
```
and objective bridges should implement:
```@docs
Bridges.set_objective_function_type
Bridges.Objective.concrete_bridge_type
Bridges.Objective.bridge_objective
```

When querying the [`NumberOfVariables`](@ref), [`NumberOfConstraints`](@ref)
and [`ListOfConstraintIndices`](@ref), the variables and constraints created
by the bridges in the underlying model are hidden by the bridge optimizer.
For this purpose, the bridge should provide access to the variables and
constraints it has creates by implemented the following methods of
[`get`](@ref):
```@docs
get(::Bridges.Constraint.AbstractBridge, ::NumberOfVariables)
get(::Bridges.Constraint.AbstractBridge, ::ListOfVariableIndices)
get(::Bridges.AbstractBridge, ::NumberOfConstraints)
get(::Bridges.AbstractBridge, ::ListOfConstraintIndices)
```
216 changes: 163 additions & 53 deletions docs/src/submodules/Bridges/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ end
DocTestFilters = [r"MathOptInterface|MOI"]
```

# The `Bridges` submodule
# The Bridges submodule

The `Bridges` module simplifies the process of converting models between
equivalent formulations.

!!! tip
[Read our paper](https://arxiv.org/abs/2002.03447) for more details on how
bridges are implemented.

## Why bridges?

A constraint can often be written in a number of equivalent formulations. For
example, the constraint ``l \le a^\top x \le u``
Expand All @@ -19,63 +28,164 @@ re-formulation is to add a dummy variable `y` with the constraints ``l \le y \le
([`ScalarAffineFunction`](@ref)-in-[`EqualTo`](@ref)).

To avoid each solver having to code these transformations manually,
MathOptInterface provides *bridges*. A bridge is a small transformation from one
constraint type to another (potentially collection of) constraint type. Because
these bridges are included in MathOptInterface, they can be re-used by any
optimizer. Some bridges also implement constraint modifications and constraint
primal and dual translations.

For example, the `SplitIntervalBridge` defines the reformulation of a
`ScalarAffineFunction`-in-`Interval` constraint into a
`ScalarAffineFunction`-in-`GreaterThan` and a
`ScalarAffineFunction`-in-`LessThan` constraint. `SplitInterval` is the
bridge optimizer that applies the `SplitIntervalBridge` rewriting rule. Given
an optimizer `optimizer` implementing `ScalarAffineFunction`-in-`GreaterThan`
and `ScalarAffineFunction`-in-`LessThan`, the optimizer
```jldoctest; setup=:(model = MOI.Utilities.Model{Float64}())
optimizer = MOI.Bridges.Constraint.SplitInterval{Float64}(model)
MOI.supports_constraint(
optimizer, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}
)

# output

true
MathOptInterface provides *bridges*.

A bridge is a small transformation from one constraint type to another
(potentially collection of) constraint type.

Because these bridges are included in MathOptInterface, they can be re-used by
any optimizer. Some bridges also implement constraint modifications and
constraint primal and dual translations.

## The three types of bridges

There are three types of bridges in MathOptInterface:
1. Constraint bridges
2. Variable bridges
3. Objective bridges

### Constraint bridges

Constraint bridges convert constraints formulated by the user into an equivalent
form supported by the solver.

The equivalent formulation may add constraints (and possibly also variables) in
the underlying model.

Read the [list of implemented constraint bridges](@ref constraint_bridges_ref)
for more details on the types of transformations that are available.

### [Variable bridges](@id variable_bridges)

Variable bridges convert variables added by the user, either free with
[`add_variable`](@ref)/[`add_variables`](@ref), or constrained with
[`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref),
into an equivalent form supported by the solver.

Te equivalent formulation may add constraints (and possibly also variables) in
the underlying model.

Read the [list of implemented variable bridges](@ref variable_bridges_ref) for
more details on the types of transformations that are available.

### Objective bridges

Objective bridges convert the [`ObjectiveFunction`](@ref) set by the user into
an equivalent form supported by the solver.

Te equivalent formulation may add constraints (and possibly also variables) in
the underlying model.

Read the [list of implemented objective bridges](@ref objective_bridges_ref) for
more details on the types of transformations that are available.

## Bridges.`full_bridge_optimizer`

!!! tip
Unless you have an advanced use-case, this is probably the only function you
need to care about.

To enable the full power of MathOptInterface's bridges, wrap an `optimizer`
in a [`Bridges.full_bridge_optimizer`](@ref).

```jldoctest
julia> inner_optimizer = MOI.Utilities.Model{Float64}()
MOIU.Model{Float64}

julia> optimizer = MOI.Bridges.full_bridge_optimizer(inner_optimizer, Float64)
MOIB.LazyBridgeOptimizer{MOIU.Model{Float64}}
with 0 variable bridges
with 0 constraint bridges
with 0 objective bridges
with inner model MOIU.Model{Float64}
```
will additionally support [`ScalarAffineFunction`](@ref)-in-[`Interval`](@ref).
Note that these [`Bridges.Constraint.SingleBridgeOptimizer`](@ref)s are mainly
used for testing bridges.

It is recommended to rather use [`Bridges.full_bridge_optimizer`](@ref) which
automatically selects the appropriate constraint bridges for unsupported
constraints.
```julia
optimizer = MOI.Bridges.full_bridge_optimizer(model, Float64)

That's all you have to do! Use `optimizer` as normal, and bridging will happen
lazily behind the scenes. By lazily, we mean that bridging will only happen if
the constraint is not supported by the `inner_optimizer`.

!!! info
Most bridges are added by default in [`Bridges.full_bridge_optimizer`](@ref).
However, for technical reasons, some bridges are not added by default. Three
examples include [`Bridges.Constraint.SOCtoPSDBridge`](@ref),
[`Bridges.Constraint.SOCtoNonConvexQuadBridge`](@ref) and
[`Bridges.Constraint.RSOCtoNonConvexQuadBridge`](@ref). See the docs of
those bridges for more information.

## Add a single bridge

If you don't want to use [`Bridges.full_bridge_optimizer`](@ref), you can wrap
an optimizer in a single bridge.

However, this will force the constraint to be bridged, even if the
`inner_optimizer` supports it.

```jldoctest
julia> inner_optimizer = MOI.Utilities.Model{Float64}()
MOIU.Model{Float64}

julia> optimizer = MOI.Bridges.Constraint.SplitInterval{Float64}(inner_optimizer)
MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64,F,S,LS,US} where US<:MOI.AbstractSet where LS<:MOI.AbstractSet where S<:MOI.AbstractSet where F<:MOI.AbstractFunction,MOIU.Model{Float64}}
with 0 constraint bridges
with inner model MOIU.Model{Float64}

julia> x = MOI.add_variable(optimizer)
MOI.VariableIndex(1)

julia> MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.Interval(0.0, 1.0))
MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Interval{Float64}}(1)

julia> MOI.get(optimizer, MOI.ListOfConstraints())
1-element Array{Tuple{DataType,DataType},1}:
(MathOptInterface.SingleVariable, MathOptInterface.Interval{Float64})

julia> MOI.get(inner_optimizer, MOI.ListOfConstraints())
2-element Array{Tuple{DataType,DataType},1}:
(MathOptInterface.SingleVariable, MathOptInterface.GreaterThan{Float64})
(MathOptInterface.SingleVariable, MathOptInterface.LessThan{Float64})
```

### Variable reformulations
## Bridges.LazyBridgeOptimizer

If you don't want to use [`Bridges.full_bridge_optimizer`](@ref), but you need
more than a single bridge (or you want the bridging to happen lazily), you can
manually construct a [`Bridges.LazyBridgeOptimizer`](@ref).

First, wrap an inner optimizer:
```jldoctest lazy_bridge_optimizer
julia> inner_optimizer = MOI.Utilities.Model{Float64}()
MOIU.Model{Float64}

A variable is often created constrained in a set unsupported by the solver while
it could be parametrized by variables constrained in supported sets.
For example, the [`Bridges.Variable.VectorizeBridge`](@ref) defines the
reformulation of a constrained variable in [`GreaterThan`](@ref) into a
constrained vector of one variable in [`Nonnegatives`](@ref).
The `Bridges.Variable.Vectorize` is the bridge optimizer that applies the
[`Bridges.Variable.VectorizeBridge`](@ref) rewriting rule. Given an optimizer
`optimizer` implementing constrained variables in [`Nonnegatives`](@ref),
the optimizer
```jldoctest; setup=:(model = MOI.Utilities.Model{Float64}())
optimizer = MOI.Bridges.Variable.Vectorize{Float64}(model)
MOI.supports_add_constrained_variable(optimizer, MOI.GreaterThan{Float64})
julia> optimizer = MOI.Bridges.LazyBridgeOptimizer(inner_optimizer)
MOIB.LazyBridgeOptimizer{MOIU.Model{Float64}}
with 0 variable bridges
with 0 constraint bridges
with 0 objective bridges
with inner model MOIU.Model{Float64}
```

# output
Then use [`Bridges.add_bridge`](@ref) to add individual bridges:
```jldoctest lazy_bridge_optimizer
julia> MOI.Bridges.add_bridge(optimizer, MOI.Bridges.Constraint.SplitIntervalBridge{Float64})
Dict{Any,DataType} with 0 entries

true
julia> MOI.Bridges.add_bridge(optimizer, MOI.Bridges.Objective.FunctionizeBridge{Float64})
Dict{Any,DataType} with 0 entries
```
will additionally support constrained variables in [`GreaterThan`](@ref).
Note that these [`Bridges.Variable.SingleBridgeOptimizer`](@ref) are mainly
used for testing bridges.

It is recommended to rather use [`Bridges.full_bridge_optimizer`](@ref), which
automatically selects the appropriate bridges for unsupported constrained
variables.
Now the constraints will be bridged only if needed:
```jldoctest lazy_bridge_optimizer
julia> x = MOI.add_variable(optimizer)
MOI.VariableIndex(1)

julia> MOI.add_constraint(optimizer, MOI.SingleVariable(x), MOI.Interval(0.0, 1.0))
MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Interval{Float64}}(1)

julia> MOI.get(optimizer, MOI.ListOfConstraints())
1-element Array{Tuple{DataType,DataType},1}:
(MathOptInterface.SingleVariable, MathOptInterface.Interval{Float64})

julia> MOI.get(inner_optimizer, MOI.ListOfConstraints())
1-element Array{Tuple{DataType,DataType},1}:
(MathOptInterface.SingleVariable, MathOptInterface.Interval{Float64})
```
Loading