Skip to content

Add variable bridges #759

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 10 commits into from
Aug 12, 2019
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
57 changes: 49 additions & 8 deletions docs/src/apimanual.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ DocTestSetup = quote
using MathOptInterface
const MOI = MathOptInterface
end
DocTestFilters = [r"MathOptInterface|MOI"]
```

# Manual
Expand Down Expand Up @@ -931,20 +932,60 @@ between `y_i` and the vector of scalar-valued quadratic functions.

### Automatic reformulation

#### Variable reformulation

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=:(optimizer = MOI.Utilities.Model{Float64}())
bridged_optimizer = MOI.Bridges.Variable.Vectorize{Float64}(optimizer)
MOI.supports_constraint(bridged_optimizer, MOI.SingleVariable, MOI.GreaterThan{Float64})

# output

true
```
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.

#### Constraint reformulation

A constraint often possess different equivalent formulations, but a solver may only support one of them.
It would be duplicate work to implement rewritting rules in every solver wrapper for every different formulation of the constraint to express it in the form supported by the solver.
Constraint bridges provide a way to define a rewritting rule on top of the MOI interface which can be used by any optimizer.
A constraint often possesses different equivalent formulations, but a solver may only support one of them.
It would be duplicate work to implement rewriting rules in every solver wrapper for every different formulation of the constraint to express it in the form supported by the solver.
Constraint bridges provide a way to define a rewriting rule on top of the MOI interface which can be used by any optimizer.
Some rules also implement constraint modifications and constraint primal and duals 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.
The `SplitInterval` is the bridge optimizer that applies the `SplitIntervalBridge` rewritting rule.
Given an optimizer `optimizer` implementing `ScalarAffineFunction`-in-`GreaterThan` and `ScalarAffineFunction`-in-`LessThan`, the optimizer
```
bridgedoptimizer = SplitInterval(optimizer)
For example, the [`Bridges.Constraint.SplitIntervalBridge`](@ref) defines the
reformulation of a [`ScalarAffineFunction`](@ref)-in-[`Interval`](@ref)
constraint into a [`ScalarAffineFunction`](@ref)-in-[`GreaterThan`](@ref) and a
[`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref) constraint.
The `Bridges.Constraint.SplitInterval` is the bridge optimizer that applies the
[`Bridges.Constraint.SplitIntervalBridge`](@ref) rewriting rule. Given an
optimizer `optimizer` implementing [`ScalarAffineFunction`](@ref)-in-[`GreaterThan`](@ref)
and [`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref), the optimizer
```jldoctest; setup=:(optimizer = MOI.Utilities.Model{Float64}())
bridged_optimizer = MOI.Bridges.Constraint.SplitInterval{Float64}(optimizer)
MOI.supports_constraint(bridged_optimizer, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})

# output

true
```
will additionally support `ScalarAffineFunction`-in-`Interval`.
Note that these [`Bridges.Constraint.SingleBridgeOptimizer`](@ref) 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.


## Implementing a solver interface

Expand Down
247 changes: 233 additions & 14 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ AbstractSubmittable
submit
```


## Model Interface

```@docs
Expand Down Expand Up @@ -192,8 +191,8 @@ delete(::ModelLike, ::Index)
[`add_variables`](@ref) while *constrained variables* are
the variables created with [`add_constrained_variable`](@ref) or
[`add_constrained_variables`](@ref). Adding constrained variables instead of
constraining free variables with [`add_constraint`](@ref) allows Variable
bridges to be used.
constraining free variables with [`add_constraint`](@ref) allows
[variable bridges](@ref variable_bridges) to be used.
Note further that free variables that are constrained with
[`add_constraint`](@ref) may be copied by [`copy_to`](@ref) with
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) by the
Expand Down Expand Up @@ -481,17 +480,220 @@ Utilities.@model

## Bridges

Bridges can be used for automatic reformulation of a certain constraint type into equivalent constraints.
Bridges can be used for automatic reformulation of constrained variables (i.e.
variables added with [`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref))
or constraints into equivalent formulations using constrained variables and
constraints of different types. There are two important concepts to distinguish:
* [`Bridges.AbstractBridge`](@ref)s are recipes implementing a specific
reformulation. Bridges are not directly subtypes of
[`Bridges.AbstractBridge`](@ref), they are either
[`Bridges.Variable.AbstractBridge`](@ref) or
[`Bridges.Constraint.AbstractBridge`](@ref).
* [`Bridges.AbstractBridgeOptimizer`](@ref) is a layer that can be applied to
another [`ModelLike`](@ref) to apply the reformulation. The
[`Bridges.LazyBridgeOptimizer`](@ref) automatically chooses the appropriate
bridges to use when a constrained variable or constraint is not supported
by using the list of bridges that were added to it by
[`Bridges.add_bridge`](@ref). [`Bridges.full_bridge_optimizer`](@ref) wraps a
model in a [`Bridges.LazyBridgeOptimizer`](@ref) where all the bridges defined
in MOI are added. This is the recommended way to use bridges in the
[Testing guideline](@ref), and JuMP automatically calls
[`Bridges.full_bridge_optimizer`](@ref) when attaching an optimizer.

```@docs
Bridges.AbstractBridge
Bridges.Constraint.AbstractBridge
Bridges.AbstractBridgeOptimizer
Bridges.Constraint.SingleBridgeOptimizer
Bridges.LazyBridgeOptimizer
Bridges.add_bridge
Bridges.full_bridge_optimizer
```

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

When variables are added, either free with
[`add_variable`](@ref)/[`add_variables`](@ref),
or constrained with
[`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref),
variable bridges allow to return *bridged variables* that do not correspond to
variables of the underlying model. These variables are parametrized by
variables of the underlying model and this parametrization can be obtained with
[`Bridges.bridged_variable_function`](@ref). Similarly, the variables of the
underlying model that were created by the bridge can be expressed in terms of
the bridged variables and this expression can be obtained with
[`Bridges.unbridged_variable_function`](@ref).
For instance, consider a model bridged by the
[`Bridges.Variable.VectorizeBridge`](@ref):
```jldoctest bridged_variable_function
model = MOI.Utilities.Model{Float64}()
bridged_model = MOI.Bridges.Variable.Vectorize{Float64}(model)
bridged_variable, bridged_constraint = MOI.add_constrained_variable(bridged_model, MOI.GreaterThan(1.0))

# output

(MOI.VariableIndex(-1), MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}(-1))
```
The constrained variable in `MOI.GreaterThan(1.0)` returned is a bridged
variable as its index in negative. In `model`, a constrained variable in
`MOI.Nonnegatives` is created:
```jldoctest bridged_variable_function
inner_variables = MOI.get(model, MOI.ListOfVariableIndices())

# output

1-element Array{MOI.VariableIndex,1}:
MOI.VariableIndex(1)
```
In the functions used for adding constraints or setting the objective to
`bridged_model`, `bridged_variable` is substituted for `inner_variables[1]` plus
1:
```jldoctest bridged_variable_function
MOI.Bridges.bridged_variable_function(bridged_model, bridged_variable)

# output

MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(1))], 1.0)
```
When getting [`ConstraintFunction`](@ref) or [`ObjectiveFunction`](@ref),
`inner_variables[1]` is substituted for `bridged_variable` minus 1:
```jldoctest bridged_variable_function
MOI.Bridges.unbridged_variable_function(bridged_model, inner_variables[1])

# output

MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1))], -1.0)
```


!!! note
A notable exception is with [`Bridges.Variable.ZerosBridge`](@ref) where no
variable is created in the underlying model as the variables are simply
transformed to zeros. When this bridge is used, it is not possible to
recover functions with bridged variables from functions of the inner
model. Consider for instance that we create two zero variables:
```jldoctest cannot_unbridge_zero
model = MOI.Utilities.Model{Float64}()
bridged_model = MOI.Bridges.Variable.Zeros{Float64}(model)
bridged_variables, bridged_constraint = MOI.add_constrained_variables(bridged_model, MOI.Zeros(2))

# output

(MOI.VariableIndex[VariableIndex(-1), VariableIndex(-2)], MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}(-1))
```
Consider the following functions in the variables of `bridged_model`:
```jldoctest cannot_unbridge_zero
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable.(bridged_variables)...)

# output

MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1)), ScalarAffineTerm{Float64}(1.0, VariableIndex(-2))], 0.0)
```
We can obtain the equivalent function in the variables of `model` as follows:
```jldoctest cannot_unbridge_zero
inner_func = MOI.Bridges.bridged_function(bridged_model, func)

# output

MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[], 0.0)
```
However, it's not possible to invert this operation. Indeed, since the
bridged variables are substituted for zeros, we cannot deduce whether
they were present in the initial function.
```jldoctest cannot_unbridge_zero; filter = r"Stacktrace:.*"s
MOI.Bridges.unbridged_function(bridged_model, inner_func)

# output

ERROR: Cannot unbridge function because some variables are bridged by variable bridges that do not support reverse mapping, e.g., `ZerosBridge`.
Stacktrace:
[1] error(::String, ::String, ::String) at ./error.jl:42
[2] throw_if_cannot_unbridge at /home/blegat/.julia/dev/MathOptInterface/src/Bridges/Variable/map.jl:343 [inlined]
[3] unbridged_function(::MOI.Bridges.Variable.SingleBridgeOptimizer{MOI.Bridges.Variable.ZerosBridge{Float64},MOI.Utilities.Model{Float64}}, ::MOI.ScalarAffineFunction{Float64}) at /home/blegat/.julia/dev/MOI/src/Bridges/bridge_optimizer.jl:920
[4] top-level scope at none:0
```

```@docs
Bridges.Variable.AbstractBridge
Bridges.bridged_variable_function
Bridges.unbridged_variable_function
```

Below is the list of bridges implemented in this package.
Below is the list of variable bridges implemented in this package.
```@docs
Bridges.Variable.ZerosBridge
Bridges.Variable.FreeBridge
Bridges.Variable.NonposToNonnegBridge
Bridges.Variable.VectorizeBridge
Bridges.Variable.RSOCtoPSDBridge
```

For each bridge defined in this package, a corresponding
[`Bridges.Variable.SingleBridgeOptimizer`](@ref) is available with the same
name without the "Bridge" suffix, e.g., `SplitInterval` is a
`SingleBridgeOptimizer` for the `SplitIntervalBridge`. Moreover, they are all
added in the [`Bridges.LazyBridgeOptimizer`](@ref) returned by
[`Bridges.full_bridge_optimizer`](@ref) as it calls
[`Bridges.Variable.add_all_bridges`](@ref).
```@docs
Bridges.Variable.SingleBridgeOptimizer
Bridges.Variable.add_all_bridges
```

### Constraint bridges

When constraints are added with [`add_constraint`](@ref), constraint bridges
allow to return *bridged constraints* that do not correspond to
constraints of the underlying model. These constraints were enforced by an
equivalent formulation that added constraints (and possibly also variables) in
the underlying model.
For instance, consider a model bridged by the
[`Bridges.Constraint.SplitIntervalBridge`](@ref):
```jldoctest split_interval
model = MOI.Utilities.Model{Float64}()
bridged_model = MOI.Bridges.Constraint.SplitInterval{Float64}(model)
x, y = MOI.add_variables(bridged_model, 2)
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable(x), MOI.SingleVariable(y))
c = MOI.add_constraint(bridged_model, func, MOI.Interval(1.0, 2.0))

# output

MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.Interval{Float64}}(1)
```
We can see the constraint was bridged to two constraints, one for each bound,
in the inner model.
```jldoctest split_interval
MOI.get(model, MOI.ListOfConstraints())

# output

2-element Array{Tuple{DataType,DataType},1}:
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
```
However, `bridged_model` transparently hides these constraints and creates the
illusion that an interval constraint was created.
```jldoctest split_interval
MOI.get(bridged_model, MOI.ListOfConstraints())

# output

1-element Array{Tuple{DataType,DataType},1}:
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
```
It is nevertheless possible to differentiate this constraint from a constraint
added to the inner model by asking whether it is bridged:
```jldoctest split_interval
MOI.Bridges.is_bridged(bridged_model, c)

# output

true
```

```@docs
Bridges.Constraint.AbstractBridge
```

Below is the list of constraint bridges implemented in this package.
```@docs
Bridges.Constraint.GreaterToLessBridge
Bridges.Constraint.LessToGreaterBridge
Expand All @@ -514,20 +716,36 @@ Bridges.Constraint.SOCtoPSDBridge
Bridges.Constraint.RSOCtoPSDBridge
Bridges.Constraint.IndicatorActiveOnFalseBridge
```
For each bridge defined in this package, a corresponding bridge optimizer is available with the same name without the "Bridge" suffix, e.g., `SplitInterval` is an `SingleBridgeOptimizer` for the `SplitIntervalBridge`.
Moreover, a `LazyBridgeOptimizer` with all the bridges defined in this package can be obtained with
For each bridge defined in this package, a corresponding
[`Bridges.Constraint.SingleBridgeOptimizer`](@ref) is available with the same
name without the "Bridge" suffix, e.g., `SplitInterval` is a
`SingleBridgeOptimizer` for the `SplitIntervalBridge`. Moreover, they are all
added in the [`Bridges.LazyBridgeOptimizer`](@ref) returned by
[`Bridges.full_bridge_optimizer`](@ref) as it calls
[`Bridges.Constraint.add_all_bridges`](@ref).
```@docs
Bridges.full_bridge_optimizer
Bridges.Constraint.SingleBridgeOptimizer
Bridges.Constraint.add_all_bridges
```

### 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
```
and constraint bridges should implement
```@docs
supports_constraint(::Type{<:Bridges.Constraint.AbstractBridge}, ::Type{<:AbstractFunction}, ::Type{<:AbstractSet})
Bridges.Constraint.concrete_bridge_type
Bridges.Constraint.bridge_constraint
Bridges.added_constraint_types
```

When querying the [`NumberOfVariables`](@ref), [`NumberOfConstraints`](@ref)
Expand All @@ -538,8 +756,9 @@ constraints it has creates by implemented the following methods of
[`get`](@ref):
```@docs
get(::Bridges.Constraint.AbstractBridge, ::NumberOfVariables)
get(::Bridges.Constraint.AbstractBridge, ::NumberOfConstraints)
get(::Bridges.Constraint.AbstractBridge, ::ListOfConstraintIndices)
get(::Bridges.Constraint.AbstractBridge, ::ListOfVariableIndices)
get(::Bridges.AbstractBridge, ::NumberOfConstraints)
get(::Bridges.AbstractBridge, ::ListOfConstraintIndices)
```

## Copy utilities
Expand Down Expand Up @@ -645,7 +864,7 @@ is set to `AUTOMATIC` or to `MANUAL`.
a constraint) results in a drop to the state `EMPTY_OPTIMIZER`.

When calling [`Utilities.attach_optimizer`](@ref), the `CachingOptimizer` copies
the cached model to the optimizer with [`MathOptInterface.copy_to`](@ref).
the cached model to the optimizer with [`copy_to`](@ref).
We refer to [Implementing copy](@ref) for more details.

```@docs
Expand Down