Skip to content

Commit 98d020b

Browse files
committed
Add variable bridges
1 parent 3b4479b commit 98d020b

File tree

2 files changed

+278
-19
lines changed

2 files changed

+278
-19
lines changed

docs/src/apimanual.md

Lines changed: 46 additions & 5 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 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+
934960
#### Constraint reformulation
935961

936962
A constraint often possess different equivalent formulations, but a solver may only support one of them.
937963
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.
938964
Constraint bridges provide a way to define a rewritting 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) 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
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 select the
987+
appropriate constraint bridges for unsupported constraints.
988+
948989

949990
## Implementing a solver interface
950991

docs/src/apireference.md

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

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

523730
### Bridge interface
524731

525732
A bridge should implement the following functions to be usable by a bridge optimizer:
526733
```@docs
734+
Bridges.added_constrained_variable_types
735+
Bridges.added_constraint_types
736+
```
737+
Additionally, variable bridges should implement:
738+
```@docs
739+
Bridges.Variable.supports_constrained_variable
740+
Bridges.Variable.concrete_bridge_type
741+
Bridges.Variable.bridge_constrained_variable
742+
```
743+
and constraint bridges should implement
744+
```@docs
527745
supports_constraint(::Type{<:Bridges.Constraint.AbstractBridge}, ::Type{<:AbstractFunction}, ::Type{<:AbstractSet})
528746
Bridges.Constraint.concrete_bridge_type
529747
Bridges.Constraint.bridge_constraint
530-
Bridges.added_constraint_types
531748
```
532749

533750
When querying the [`NumberOfVariables`](@ref), [`NumberOfConstraints`](@ref)
@@ -538,8 +755,9 @@ constraints it has creates by implemented the following methods of
538755
[`get`](@ref):
539756
```@docs
540757
get(::Bridges.Constraint.AbstractBridge, ::NumberOfVariables)
541-
get(::Bridges.Constraint.AbstractBridge, ::NumberOfConstraints)
542-
get(::Bridges.Constraint.AbstractBridge, ::ListOfConstraintIndices)
758+
get(::Bridges.Constraint.AbstractBridge, ::ListOfVariableIndices)
759+
get(::Bridges.AbstractBridge, ::NumberOfConstraints)
760+
get(::Bridges.AbstractBridge, ::ListOfConstraintIndices)
543761
```
544762

545763
## Copy utilities
@@ -645,7 +863,7 @@ is set to `AUTOMATIC` or to `MANUAL`.
645863
a constraint) results in a drop to the state `EMPTY_OPTIMIZER`.
646864

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

651869
```@docs

0 commit comments

Comments
 (0)