diff --git a/docs/src/apimanual.md b/docs/src/apimanual.md index 0bf04755d0..d2e97dfa0e 100644 --- a/docs/src/apimanual.md +++ b/docs/src/apimanual.md @@ -4,6 +4,7 @@ DocTestSetup = quote using MathOptInterface const MOI = MathOptInterface end +DocTestFilters = [r"MathOptInterface|MOI"] ``` # Manual @@ -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 diff --git a/docs/src/apireference.md b/docs/src/apireference.md index ee8ef286ce..90275a08de 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -74,7 +74,6 @@ AbstractSubmittable submit ``` - ## Model Interface ```@docs @@ -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 @@ -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 @@ -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) @@ -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 @@ -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