Skip to content

Commit d9673e8

Browse files
authored
Merge pull request #759 from JuliaOpt/bl/variable_bridge
Add variable bridges
2 parents af426f0 + 71437dd commit d9673e8

File tree

2 files changed

+282
-22
lines changed

2 files changed

+282
-22
lines changed

docs/src/apimanual.md

Lines changed: 49 additions & 8 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
@@ -931,20 +932,60 @@ between `y_i` and the vector of scalar-valued quadratic functions.
931932

932933
### Automatic reformulation
933934

935+
#### Variable reformulation
936+
937+
A variable is often created constrained in a set unsupported by the solver while
938+
it could be 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) rewriting rule. Given an optimizer
944+
`optimizer` implementing constrained variables in [`Nonnegatives`](@ref),
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 selects the
958+
appropriate bridges for unsupported constrained variables.
959+
934960
#### Constraint reformulation
935961

936-
A constraint often possess different equivalent formulations, but a solver may only support one of them.
937-
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.
938-
Constraint bridges provide a way to define a rewritting rule on top of the MOI interface which can be used by any optimizer.
962+
A constraint often possesses different equivalent formulations, but a solver may only support one of them.
963+
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.
964+
Constraint bridges provide a way to define a rewriting rule on top of the MOI interface which can be used by any optimizer.
939965
Some rules also implement constraint modifications and constraint primal and duals translations.
940966

941-
For example, the `SplitIntervalBridge` defines the reformulation of a `ScalarAffineFunction`-in-`Interval` constraint into a `ScalarAffineFunction`-in-`GreaterThan` and a `ScalarAffineFunction`-in-`LessThan` constraint.
942-
The `SplitInterval` is the bridge optimizer that applies the `SplitIntervalBridge` rewritting rule.
943-
Given an optimizer `optimizer` implementing `ScalarAffineFunction`-in-`GreaterThan` and `ScalarAffineFunction`-in-`LessThan`, the optimizer
944-
```
945-
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) rewriting 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
946982
```
947983
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 selects the
987+
appropriate constraint bridges for unsupported constraints.
988+
948989

949990
## Implementing a solver interface
950991

docs/src/apireference.md

Lines changed: 233 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ AbstractSubmittable
7474
submit
7575
```
7676

77-
7877
## Model Interface
7978

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

482481
## Bridges
483482

484-
Bridges can be used for automatic reformulation of a certain constraint type into equivalent constraints.
483+
Bridges can be used for automatic reformulation of constrained variables (i.e.
484+
variables added with [`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref))
485+
or constraints into equivalent formulations using constrained variables and
486+
constraints of different types. There are two important concepts to distinguish:
487+
* [`Bridges.AbstractBridge`](@ref)s are recipes implementing a specific
488+
reformulation. Bridges are not directly subtypes of
489+
[`Bridges.AbstractBridge`](@ref), they are either
490+
[`Bridges.Variable.AbstractBridge`](@ref) or
491+
[`Bridges.Constraint.AbstractBridge`](@ref).
492+
* [`Bridges.AbstractBridgeOptimizer`](@ref) is a layer that can be applied to
493+
another [`ModelLike`](@ref) to apply the reformulation. The
494+
[`Bridges.LazyBridgeOptimizer`](@ref) automatically chooses the appropriate
495+
bridges to use when a constrained variable or constraint is not supported
496+
by using the list of bridges that were added to it by
497+
[`Bridges.add_bridge`](@ref). [`Bridges.full_bridge_optimizer`](@ref) wraps a
498+
model in a [`Bridges.LazyBridgeOptimizer`](@ref) where all the bridges defined
499+
in MOI are added. This is the recommended way to use bridges in the
500+
[Testing guideline](@ref), and JuMP automatically calls
501+
[`Bridges.full_bridge_optimizer`](@ref) when attaching an optimizer.
502+
485503
```@docs
486504
Bridges.AbstractBridge
487-
Bridges.Constraint.AbstractBridge
488505
Bridges.AbstractBridgeOptimizer
489-
Bridges.Constraint.SingleBridgeOptimizer
490506
Bridges.LazyBridgeOptimizer
491507
Bridges.add_bridge
508+
Bridges.full_bridge_optimizer
509+
```
510+
511+
### [Variable bridges](@id variable_bridges)
512+
513+
When variables are added, either free with
514+
[`add_variable`](@ref)/[`add_variables`](@ref),
515+
or constrained with
516+
[`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref),
517+
variable bridges allow to return *bridged variables* that do not correspond to
518+
variables of the underlying model. These variables are parametrized by
519+
variables of the underlying model and this parametrization can be obtained with
520+
[`Bridges.bridged_variable_function`](@ref). Similarly, the variables of the
521+
underlying model that were created by the bridge can be expressed in terms of
522+
the bridged variables and this expression can be obtained with
523+
[`Bridges.unbridged_variable_function`](@ref).
524+
For instance, consider a model bridged by the
525+
[`Bridges.Variable.VectorizeBridge`](@ref):
526+
```jldoctest bridged_variable_function
527+
model = MOI.Utilities.Model{Float64}()
528+
bridged_model = MOI.Bridges.Variable.Vectorize{Float64}(model)
529+
bridged_variable, bridged_constraint = MOI.add_constrained_variable(bridged_model, MOI.GreaterThan(1.0))
530+
531+
# output
532+
533+
(MOI.VariableIndex(-1), MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}(-1))
534+
```
535+
The constrained variable in `MOI.GreaterThan(1.0)` returned is a bridged
536+
variable as its index in negative. In `model`, a constrained variable in
537+
`MOI.Nonnegatives` is created:
538+
```jldoctest bridged_variable_function
539+
inner_variables = MOI.get(model, MOI.ListOfVariableIndices())
540+
541+
# output
542+
543+
1-element Array{MOI.VariableIndex,1}:
544+
MOI.VariableIndex(1)
545+
```
546+
In the functions used for adding constraints or setting the objective to
547+
`bridged_model`, `bridged_variable` is substituted for `inner_variables[1]` plus
548+
1:
549+
```jldoctest bridged_variable_function
550+
MOI.Bridges.bridged_variable_function(bridged_model, bridged_variable)
551+
552+
# output
553+
554+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(1))], 1.0)
555+
```
556+
When getting [`ConstraintFunction`](@ref) or [`ObjectiveFunction`](@ref),
557+
`inner_variables[1]` is substituted for `bridged_variable` minus 1:
558+
```jldoctest bridged_variable_function
559+
MOI.Bridges.unbridged_variable_function(bridged_model, inner_variables[1])
560+
561+
# output
562+
563+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1))], -1.0)
564+
```
565+
566+
567+
!!! note
568+
A notable exception is with [`Bridges.Variable.ZerosBridge`](@ref) where no
569+
variable is created in the underlying model as the variables are simply
570+
transformed to zeros. When this bridge is used, it is not possible to
571+
recover functions with bridged variables from functions of the inner
572+
model. Consider for instance that we create two zero variables:
573+
```jldoctest cannot_unbridge_zero
574+
model = MOI.Utilities.Model{Float64}()
575+
bridged_model = MOI.Bridges.Variable.Zeros{Float64}(model)
576+
bridged_variables, bridged_constraint = MOI.add_constrained_variables(bridged_model, MOI.Zeros(2))
577+
578+
# output
579+
580+
(MOI.VariableIndex[VariableIndex(-1), VariableIndex(-2)], MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}(-1))
581+
```
582+
Consider the following functions in the variables of `bridged_model`:
583+
```jldoctest cannot_unbridge_zero
584+
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable.(bridged_variables)...)
585+
586+
# output
587+
588+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(1.0, VariableIndex(-1)), ScalarAffineTerm{Float64}(1.0, VariableIndex(-2))], 0.0)
589+
```
590+
We can obtain the equivalent function in the variables of `model` as follows:
591+
```jldoctest cannot_unbridge_zero
592+
inner_func = MOI.Bridges.bridged_function(bridged_model, func)
593+
594+
# output
595+
596+
MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}[], 0.0)
597+
```
598+
However, it's not possible to invert this operation. Indeed, since the
599+
bridged variables are substituted for zeros, we cannot deduce whether
600+
they were present in the initial function.
601+
```jldoctest cannot_unbridge_zero; filter = r"Stacktrace:.*"s
602+
MOI.Bridges.unbridged_function(bridged_model, inner_func)
603+
604+
# output
605+
606+
ERROR: Cannot unbridge function because some variables are bridged by variable bridges that do not support reverse mapping, e.g., `ZerosBridge`.
607+
Stacktrace:
608+
[1] error(::String, ::String, ::String) at ./error.jl:42
609+
[2] throw_if_cannot_unbridge at /home/blegat/.julia/dev/MathOptInterface/src/Bridges/Variable/map.jl:343 [inlined]
610+
[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
611+
[4] top-level scope at none:0
612+
```
613+
614+
```@docs
615+
Bridges.Variable.AbstractBridge
616+
Bridges.bridged_variable_function
617+
Bridges.unbridged_variable_function
492618
```
493619

494-
Below is the list of bridges implemented in this package.
620+
Below is the list of variable bridges implemented in this package.
621+
```@docs
622+
Bridges.Variable.ZerosBridge
623+
Bridges.Variable.FreeBridge
624+
Bridges.Variable.NonposToNonnegBridge
625+
Bridges.Variable.VectorizeBridge
626+
Bridges.Variable.RSOCtoPSDBridge
627+
```
628+
629+
For each bridge defined in this package, a corresponding
630+
[`Bridges.Variable.SingleBridgeOptimizer`](@ref) is available with the same
631+
name without the "Bridge" suffix, e.g., `SplitInterval` is a
632+
`SingleBridgeOptimizer` for the `SplitIntervalBridge`. Moreover, they are all
633+
added in the [`Bridges.LazyBridgeOptimizer`](@ref) returned by
634+
[`Bridges.full_bridge_optimizer`](@ref) as it calls
635+
[`Bridges.Variable.add_all_bridges`](@ref).
636+
```@docs
637+
Bridges.Variable.SingleBridgeOptimizer
638+
Bridges.Variable.add_all_bridges
639+
```
640+
641+
### Constraint bridges
642+
643+
When constraints are added with [`add_constraint`](@ref), constraint bridges
644+
allow to return *bridged constraints* that do not correspond to
645+
constraints of the underlying model. These constraints were enforced by an
646+
equivalent formulation that added constraints (and possibly also variables) in
647+
the underlying model.
648+
For instance, consider a model bridged by the
649+
[`Bridges.Constraint.SplitIntervalBridge`](@ref):
650+
```jldoctest split_interval
651+
model = MOI.Utilities.Model{Float64}()
652+
bridged_model = MOI.Bridges.Constraint.SplitInterval{Float64}(model)
653+
x, y = MOI.add_variables(bridged_model, 2)
654+
func = MOI.Utilities.operate(+, Float64, MOI.SingleVariable(x), MOI.SingleVariable(y))
655+
c = MOI.add_constraint(bridged_model, func, MOI.Interval(1.0, 2.0))
656+
657+
# output
658+
659+
MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.Interval{Float64}}(1)
660+
```
661+
We can see the constraint was bridged to two constraints, one for each bound,
662+
in the inner model.
663+
```jldoctest split_interval
664+
MOI.get(model, MOI.ListOfConstraints())
665+
666+
# output
667+
668+
2-element Array{Tuple{DataType,DataType},1}:
669+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})
670+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
671+
```
672+
However, `bridged_model` transparently hides these constraints and creates the
673+
illusion that an interval constraint was created.
674+
```jldoctest split_interval
675+
MOI.get(bridged_model, MOI.ListOfConstraints())
676+
677+
# output
678+
679+
1-element Array{Tuple{DataType,DataType},1}:
680+
(MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
681+
```
682+
It is nevertheless possible to differentiate this constraint from a constraint
683+
added to the inner model by asking whether it is bridged:
684+
```jldoctest split_interval
685+
MOI.Bridges.is_bridged(bridged_model, c)
686+
687+
# output
688+
689+
true
690+
```
691+
692+
```@docs
693+
Bridges.Constraint.AbstractBridge
694+
```
695+
696+
Below is the list of constraint bridges implemented in this package.
495697
```@docs
496698
Bridges.Constraint.GreaterToLessBridge
497699
Bridges.Constraint.LessToGreaterBridge
@@ -514,20 +716,36 @@ Bridges.Constraint.SOCtoPSDBridge
514716
Bridges.Constraint.RSOCtoPSDBridge
515717
Bridges.Constraint.IndicatorActiveOnFalseBridge
516718
```
517-
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`.
518-
Moreover, a `LazyBridgeOptimizer` with all the bridges defined in this package can be obtained with
719+
For each bridge defined in this package, a corresponding
720+
[`Bridges.Constraint.SingleBridgeOptimizer`](@ref) is available with the same
721+
name without the "Bridge" suffix, e.g., `SplitInterval` is a
722+
`SingleBridgeOptimizer` for the `SplitIntervalBridge`. Moreover, they are all
723+
added in the [`Bridges.LazyBridgeOptimizer`](@ref) returned by
724+
[`Bridges.full_bridge_optimizer`](@ref) as it calls
725+
[`Bridges.Constraint.add_all_bridges`](@ref).
519726
```@docs
520-
Bridges.full_bridge_optimizer
727+
Bridges.Constraint.SingleBridgeOptimizer
728+
Bridges.Constraint.add_all_bridges
521729
```
522730

523731
### Bridge interface
524732

525733
A bridge should implement the following functions to be usable by a bridge optimizer:
526734
```@docs
735+
Bridges.added_constrained_variable_types
736+
Bridges.added_constraint_types
737+
```
738+
Additionally, variable bridges should implement:
739+
```@docs
740+
Bridges.Variable.supports_constrained_variable
741+
Bridges.Variable.concrete_bridge_type
742+
Bridges.Variable.bridge_constrained_variable
743+
```
744+
and constraint bridges should implement
745+
```@docs
527746
supports_constraint(::Type{<:Bridges.Constraint.AbstractBridge}, ::Type{<:AbstractFunction}, ::Type{<:AbstractSet})
528747
Bridges.Constraint.concrete_bridge_type
529748
Bridges.Constraint.bridge_constraint
530-
Bridges.added_constraint_types
531749
```
532750

533751
When querying the [`NumberOfVariables`](@ref), [`NumberOfConstraints`](@ref)
@@ -538,8 +756,9 @@ constraints it has creates by implemented the following methods of
538756
[`get`](@ref):
539757
```@docs
540758
get(::Bridges.Constraint.AbstractBridge, ::NumberOfVariables)
541-
get(::Bridges.Constraint.AbstractBridge, ::NumberOfConstraints)
542-
get(::Bridges.Constraint.AbstractBridge, ::ListOfConstraintIndices)
759+
get(::Bridges.Constraint.AbstractBridge, ::ListOfVariableIndices)
760+
get(::Bridges.AbstractBridge, ::NumberOfConstraints)
761+
get(::Bridges.AbstractBridge, ::ListOfConstraintIndices)
543762
```
544763

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

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

651870
```@docs

0 commit comments

Comments
 (0)