Skip to content

Commit

Permalink
Merge pull request #759 from JuliaOpt/bl/variable_bridge
Browse files Browse the repository at this point in the history
Add variable bridges
  • Loading branch information
blegat authored Aug 12, 2019
2 parents af426f0 + 71437dd commit d9673e8
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 22 deletions.
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

0 comments on commit d9673e8

Please sign in to comment.