Skip to content

Commit bae657e

Browse files
committed
Address @mlubin comments
1 parent cc2f86f commit bae657e

File tree

6 files changed

+281
-80
lines changed

6 files changed

+281
-80
lines changed

docs/src/apimanual.md

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ DocTestSetup = quote
44
using MathOptInterface
55
const MOI = MathOptInterface
66
end
7+
DocTestFilters = [r"MathOptInterface|MOI"]
78
```
89

910
# Manual
@@ -929,20 +930,62 @@ If ``\mathcal{C}_i`` is a vector set, the discussion remains valid with
929930
``y_i(\frac{1}{2}x^TQ_ix + a_i^T x + b_i)`` replaced with the scalar product
930931
between `y_i` and the vector of scalar-valued quadratic functions.
931932

932-
### Constraint bridges
933+
### Automatic reformulation
934+
935+
#### Variable reformulation
936+
937+
A variable is often created in a set unsupported by the solver while it could be
938+
parametrized by variables constrained in supported sets.
939+
For example, the [`Bridges.Variable.VectorizeBridge`](@ref) defines the
940+
reformulation of a constrained variable in [`GreaterThan`](@ref) into a
941+
constrained vector of one variable in [`Nonnegatives`](@ref).
942+
The `Bridges.Variable.Vectorize` is the bridge optimizer that applies the
943+
[`Bridges.Variable.VectorizeBridge`](@ref) rewritting rule. Given an optimizer
944+
`optimizer` implementing constrained variables in [`Nonnegatives`](@ref) and,
945+
the optimizer
946+
```jldoctest; setup=:(optimizer = MOI.Utilities.Model{Float64}())
947+
bridged_optimizer = MOI.Bridges.Variable.Vectorize{Float64}(optimizer)
948+
MOI.supports_constraint(bridged_optimizer, MOI.SingleVariable, MOI.GreaterThan{Float64})
949+
950+
# output
951+
952+
true
953+
```
954+
will additionally support constrained variables in [`GreaterThan`](@ref).
955+
Note that these [`Bridges.Variable.SingleBridgeOptimizer`](@ref) are mainly
956+
used for testing bridges. It is recommended to rather use
957+
[`Bridges.full_bridge_optimizer`](@ref) which automatically select the
958+
appropriate bridges for unsupported constrained variables.
959+
960+
#### Constraint reformulation
933961

934962
A constraint often possess different equivalent formulations, but a solver may only support one of them.
935963
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.
936964
Constraint bridges provide a way to define a rewritting rule on top of the MOI interface which can be used by any optimizer.
937965
Some rules also implement constraint modifications and constraint primal and duals translations.
938966

939-
For example, the `SplitIntervalBridge` defines the reformulation of a `ScalarAffineFunction`-in-`Interval` constraint into a `ScalarAffineFunction`-in-`GreaterThan` and a `ScalarAffineFunction`-in-`LessThan` constraint.
940-
The `SplitInterval` is the bridge optimizer that applies the `SplitIntervalBridge` rewritting rule.
941-
Given an optimizer `optimizer` implementing `ScalarAffineFunction`-in-`GreaterThan` and `ScalarAffineFunction`-in-`LessThan`, the optimizer
942-
```
943-
bridgedoptimizer = SplitInterval(optimizer)
967+
For example, the [`Bridges.Constraint.SplitIntervalBridge`](@ref) defines the
968+
reformulation of a [`ScalarAffineFunction`](@ref)-in-[`Interval`](@ref)
969+
constraint into a [`ScalarAffineFunction`](@ref)-in-[`GreaterThan`](@ref) and a
970+
[`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref) constraint.
971+
The `Bridges.Constraint.SplitInterval` is the bridge optimizer that applies the
972+
[`Bridges.Constraint.SplitIntervalBridge`](@ref) rewritting rule. Given an
973+
optimizer `optimizer` implementing [`ScalarAffineFunction`](@ref)-in-[`GreaterThan`](@ref)
974+
and [`ScalarAffineFunction`](@ref)-in-[`LessThan`](@ref), the optimizer
975+
```jldoctest; setup=:(optimizer = MOI.Utilities.Model{Float64}())
976+
bridged_optimizer = MOI.Bridges.Constraint.SplitInterval{Float64}(optimizer)
977+
MOI.supports_constraint(bridged_optimizer, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
978+
979+
# output
980+
981+
true
944982
```
945983
will additionally support `ScalarAffineFunction`-in-`Interval`.
984+
Note that these [`Bridges.Constraint.SingleBridgeOptimizer`](@ref) are mainly
985+
used for testing bridges. It is recommended to rather use
986+
[`Bridges.full_bridge_optimizer`](@ref) which automatically select the
987+
appropriate constraint bridges for unsupported constraints.
988+
946989

947990
## Implementing a solver interface
948991

@@ -966,6 +1009,34 @@ model = MyPackage.Optimizer()
9661009
MOI.set(model, MyPackage.PrintLevel(), 0)
9671010
```
9681011

1012+
### Supported constrained variables and constraints
1013+
1014+
The solver interface should only implement support for constrained variables
1015+
or constraints that directly map to a structure exploited by the solver
1016+
algorithm. There is no need to add support for additional types, this is
1017+
handled by the [Automatic reformulation](@ref). Furthermore, this allows
1018+
[`supports_constraint`](@ref) to indicate which types are exploited by the
1019+
solver and hence allow layers such as [`Bridges.LazyBridgeOptimizer`](@ref)
1020+
to accurately select the most appropriate transformations.
1021+
1022+
As [`add_constrained_variable`](@ref) (resp. [`add_constrained_variables`](@ref))
1023+
falls back to [`add_variable`](@ref) (resp. [`add_variables`](@ref)) followed by
1024+
[`add_constraint`](@ref), there is no need to implement this function
1025+
if `model` supports creating free variables and supports transforming free
1026+
variables into variables in `set`. However,
1027+
1028+
* if `model` does not support creating free variables, it should only implement
1029+
`add_constrained_variable` and not [`add_variable`](@ref) nor
1030+
[`add_constraint`](@ref) for [`SingleVariable`](@ref)-in-`typeof(set)`.
1031+
In addition, it should implement `supports_constraint(::Optimizer,
1032+
::Type{VectorOfVariables}, ::Type{Reals})` and return `false` so that free
1033+
variables are bridged, see [`supports_constraint`](@ref).
1034+
* if `model` supports free variables but does not support transforming free
1035+
variables into variables in `set`, then it should implement both
1036+
[`add_variable`](@ref) and `add_constraint_variable` but should not implement
1037+
any method for [`add_constraint`](@ref) for
1038+
[`SingleVariable`](@ref)-in-`typeof(set)`.
1039+
9691040
### Implementing copy
9701041

9711042
Avoid storing extra copies of the problem when possible. This means that solver

docs/src/apireference.md

Lines changed: 158 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,25 @@ delete(::ModelLike, ::Index)
186186

187187
### Variables
188188

189-
Functions for adding variables. For deleting, see index types section.
189+
*Free variables* are created with with [`add_variable`](@ref) or
190+
[`add_variables`](@ref) and *constrained variables* are created with
191+
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref).
192+
Note that free variables can be constrained after being created using
193+
[`add_constraint`](@ref) with the [`SingleVariable`](@ref) or
194+
[`VectorOfVariables`](@ref). However, they need to be added with
195+
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) to
196+
allow [Variable bridges](@ref) to be used.
197+
Note further that free variables that are constrained after being created using
198+
[`add_constraint`](@ref) will be copied by [`copy_to`](@ref) with
199+
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) by the
200+
[`Utilities.CachingOptimizer`](@ref).
201+
For deleting, see index types section.
190202

191203
```@docs
192-
add_variables
193204
add_variable
194-
add_constrained_variables
205+
add_variables
195206
add_constrained_variable
207+
add_constrained_variables
196208
```
197209

198210
List of attributes associated with variables. [category AbstractVariableAttribute]
@@ -462,24 +474,23 @@ Utilities.@model
462474

463475
## Bridges
464476

465-
Bridges can be used for automatic reformulation of a certain constrained
466-
variables or constraints into equivalent formulations using constrained
467-
variables and constraints of different types.
468-
There are two important concepts to distinguish:
477+
Bridges can be used for automatic reformulation of constrained variables or
478+
constraints into equivalent formulations using constrained variables and
479+
constraints of different types. There are two important concepts to distinguish:
469480
* [`Bridges.AbstractBridge`](@ref)s are recipes implementing a specific
470481
reformulation. Bridges are not directly subtypes of
471482
[`Bridges.AbstractBridge`](@ref), they are either
472483
[`Bridges.Variable.AbstractBridge`](@ref) or
473484
[`Bridges.Constraint.AbstractBridge`](@ref).
474-
* [`Bridges.AbstractBridgeOptimizer`](@ref)s is a layer that can applied to
485+
* [`Bridges.AbstractBridgeOptimizer`](@ref) is a layer that can be applied to
475486
another [`ModelLike`](@ref) to apply the reformulation. The
476-
[`Bridges.LazyBridgeOptimizer`](@ref) automatically choose the appropriate
487+
[`Bridges.LazyBridgeOptimizer`](@ref) automatically chooses the appropriate
477488
bridges to use when a constrained variable or constraint is not supported
478-
using the list of bridges that were added to it by [`Bridges.add_bridge`](@ref).
479-
[`Bridges.full_bridge_optimizer`](@ref) wraps a model in a
480-
[`Bridges.LazyBridgeOptimizer`](@ref) where all the bridges defined in MOI
481-
are added. This is the recommended way to use bridges in the
482-
[Testing guideline](@ref) and JuMP automatically calls this function when
489+
by using the list of bridges that were added to it by
490+
[`Bridges.add_bridge`](@ref). [`Bridges.full_bridge_optimizer`](@ref) wraps a
491+
model in a [`Bridges.LazyBridgeOptimizer`](@ref) where all the bridges defined
492+
in MOI are added. This is the recommended way to use bridges in the
493+
[Testing guideline](@ref), and JuMP automatically calls this function when
483494
attaching an optimizer.
484495

485496
```@docs
@@ -499,22 +510,104 @@ or constrained with
499510
variable bridges allow to return *bridged variables* that do not correspond to
500511
variables of the underlying model. These variables are parametrized by
501512
variables of the underlying model and this parametrization can be obtained with
502-
[`Bridges.variable_bridged_function`](@ref). Similarly, the variables of the
513+
[`Bridges.bridged_variable_function`](@ref). Similarly, the variables of the
503514
underlying model that were created by the bridge can be expressed in terms of
504515
the bridged variables and this expression can be obtained with
505-
[`Bridges.variable_unbridged_function`](@ref).
516+
[`Bridges.unbridged_variable_function`](@ref).
517+
For instance, consider a model bridged by the
518+
[`Bridges.Variable.VectorizeBridge`](@ref):
519+
```jldoctest bridged_variable_function
520+
model = MOI.Utilities.Model{Float64}()
521+
bridged_model = MOI.Bridges.Variable.Vectorize{Float64}(model)
522+
bridged_variable, bridged_constraint = MOI.add_constrained_variable(bridged_model, MOI.GreaterThan(1.0))
523+
524+
# output
525+
526+
(MOI.VariableIndex(-1), MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}(-1))
527+
```
528+
The constrained variable in `MOI.GreaterThan(1.0)` returned is a bridged
529+
variable as its index in negative. In `model`, a constrained variable in
530+
`MOI.Nonnegatives` is created:
531+
```jldoctest bridged_variable_function
532+
inner_variables = MOI.get(model, MOI.ListOfVariableIndices())
533+
534+
# output
535+
536+
1-element Array{MOI.VariableIndex,1}:
537+
MOI.VariableIndex(1)
538+
```
539+
In the functions used for adding constraints or setting the objective to
540+
`bridged_model`, `bridged_variable` is substituted for `inner_variables[1]` plus
541+
1:
542+
```jldoctest bridged_variable_function
543+
MOI.Bridges.bridged_variable_function(bridged_model, bridged_variable)
544+
545+
# output
546+
547+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(1))], 1.0)
548+
```
549+
When getting [`ConstraintFunction`](@ref) or [`ObjectiveFunction`](@ref),
550+
`inner_variables[1]` is substituted for `bridged_variable` minus 1:
551+
```jldoctest bridged_variable_function
552+
MOI.Bridges.unbridged_variable_function(bridged_model, inner_variables[1])
553+
554+
# output
555+
556+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1))], -1.0)
557+
```
558+
506559

507560
!!! note
508561
A notable exception is with [`Bridges.Variable.ZerosBridge`](@ref) where no
509562
variable is created in the underlying model as the variables are simply
510563
transformed to zeros. When this bridge is used, it is not possible to
511-
recover functions with bridged variables from functions of the underlying
512-
model.
564+
recover functions with bridged variables from functions of the inner
565+
model. Consider for instance that we create two zero variables:
566+
```jldoctest cannot_unbridge_zero
567+
model = MOI.Utilities.Model{Float64}()
568+
bridged_model = MOI.Bridges.Variable.Zeros{Float64}(model)
569+
bridged_variables, bridged_constraint = MOI.add_constrained_variables(bridged_model, MOI.Zeros(2))
570+
571+
# output
572+
573+
(MOI.VariableIndex[VariableIndex(-1), VariableIndex(-2)], MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}(-1))
574+
```
575+
Consider the following functions in the variables of `bridged_model`:
576+
```jldoctest cannot_unbridge_zero
577+
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable.(bridged_variables)...)
578+
579+
# output
580+
581+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1)), ScalarAffineTerm{Float64}(1.0, VariableIndex(-2))], 0.0)
582+
```
583+
We can obtain the equivalent function in the variables of `model` as follows:
584+
```jldoctest cannot_unbridge_zero
585+
inner_func = MOI.Bridges.bridged_function(bridged_model, func)
586+
587+
# output
588+
589+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[], 0.0)
590+
```
591+
However, it's not possible to invert this operations. Indeed, since the
592+
bridged variables are substituted for zeros, we cannot deduce whether
593+
they were present in the initial function.
594+
```jldoctest cannot_unbridge_zero; filter = r"Stacktrace:.*"s
595+
MOI.Bridges.unbridged_function(bridged_model, inner_func)
596+
597+
# output
598+
599+
ERROR: Cannot unbridge function because some variables are bridged by variable bridges that do not support reverse mapping, e.g., `ZerosBridge`.
600+
Stacktrace:
601+
[1] error(::String, ::String, ::String) at ./error.jl:42
602+
[2] throw_if_cannot_unbridge at /home/blegat/.julia/dev/MathOptInterface/src/Bridges/Variable/map.jl:343 [inlined]
603+
[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
604+
[4] top-level scope at none:0
605+
```
513606

514607
```@docs
515608
Bridges.Variable.AbstractBridge
516-
Bridges.variable_bridged_function
517-
Bridges.variable_unbridged_function
609+
Bridges.bridged_variable_function
610+
Bridges.unbridged_variable_function
518611
```
519612

520613
Below is the list of variable bridges implemented in this package.
@@ -545,6 +638,50 @@ allow to return *bridged constraints* that do not correspond to
545638
constraints of the underlying model. These constraints were enforced by an
546639
equivalent formulation that added constraints (and possibly also variables) in
547640
the underlying model.
641+
For instance, consider a model bridged by the
642+
[`Bridges.Constraint.SplitIntervalBridge`](@ref):
643+
```jldoctest split_interval
644+
model = MOI.Utilities.Model{Float64}()
645+
bridged_model = MOI.Bridges.Constraint.SplitInterval{Float64}(model)
646+
x, y = MOI.add_variables(bridged_model, 2)
647+
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable(x), MOI.SingleVariable(y))
648+
c = MOI.add_constraint(bridged_model, func, MOI.Interval(1.0, 2.0))
649+
650+
# output
651+
652+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.Interval{Float64}}(1)
653+
```
654+
We can see the constraint was bridged to two constraints, one for each bound,
655+
in the inner model.
656+
```jldoctest split_interval
657+
MOI.get(model, MOI.ListOfConstraints())
658+
659+
# output
660+
661+
2-element Array{Tuple{DataType,DataType},1}:
662+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})
663+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
664+
```
665+
However, `bridged_model` hides transparently hides these constraints and create the
666+
illusion that an interval constraint was created.
667+
```jldoctest split_interval
668+
MOI.get(bridged_model, MOI.ListOfConstraints())
669+
670+
# output
671+
672+
1-element Array{Tuple{DataType,DataType},1}:
673+
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
674+
```
675+
It is nevertheless possible to differentiate this constraint from a constraint
676+
added to the inner model by asking whether it is bridged:
677+
```jldoctest split_interval
678+
MOI.Bridges.is_bridged(bridged_model, c)
679+
680+
# output
681+
682+
true
683+
```
684+
548685
```@docs
549686
Bridges.Constraint.AbstractBridge
550687
```
@@ -720,7 +857,7 @@ is set to `AUTOMATIC` or to `MANUAL`.
720857
a constraint) results in a drop to the state `EMPTY_OPTIMIZER`.
721858

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

726863
```@docs

0 commit comments

Comments
 (0)