Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/src/apimanual.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,9 @@ If ``\mathcal{C}_i`` is a vector set, the discussion remains valid with
``y_i(\frac{1}{2}x^TQ_ix + a_i^T x + b_i)`` replaced with the scalar product
between `y_i` and the vector of scalar-valued quadratic functions.

### Constraint bridges
### Automatic reformulation

#### 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.
Expand Down Expand Up @@ -966,6 +968,28 @@ model = MyPackage.Optimizer()
MOI.set(model, MyPackage.PrintLevel(), 0)
```

### Supported constrained variables and constraints

The solver interface should only implement support for constrained variables
(see [`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref))
or constraints that directly map to a structure exploited by the solver
algorithm. There is no need to add support for additional types, this is
handled by the [Automatic reformulation](@ref). Furthermore, this allows
[`supports_constraint`](@ref) to indicate which types are exploited by the
solver and hence allows layers such as [`Bridges.LazyBridgeOptimizer`](@ref)
to accurately select the most appropriate transformations.

As [`add_constrained_variable`](@ref) (resp. [`add_constrained_variables`](@ref))
falls back to [`add_variable`](@ref) (resp. [`add_variables`](@ref)) followed by
[`add_constraint`](@ref), there is no need to implement this function
if `model` supports creating free variables. However, if `model` does not
support creating free variables, then it should only implement
[`add_constrained_variable`](@ref) and not [`add_variable`](@ref) nor
[`add_constraint`](@ref) for [`SingleVariable`](@ref)-in-`typeof(set)`.
In addition, it should implement `supports_constraint(::Optimizer,
::Type{VectorOfVariables}, ::Type{Reals})` and return `false` so that free
variables are bridged, see [`supports_constraint`](@ref).

### Implementing copy

Avoid storing extra copies of the problem when possible. This means that solver
Expand Down
22 changes: 20 additions & 2 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,29 @@ delete(::ModelLike, ::Index)

### Variables

Functions for adding variables. For deleting, see index types section.
*Free variables* are the variables created with [`add_variable`](@ref) or
[`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.
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
[`Utilities.CachingOptimizer`](@ref).
More precisely, the attributes do not distinguish constraints on variables
created with `add_constrained_variable(s)` or `add_variable(s)`/`add_constraint`.
When the model is copied, if a variable is constrained in several sets,
the implementation of [`copy_to`](@ref) can determine whether it is added
using [`add_variable`](@ref) or [`add_constrained_variable`](@ref) with one
of the sets. The rest of the constraints on the variables are added
with [`add_constraint`](@ref). For deleting, see [Index types](@ref).

```@docs
add_variables
add_variable
add_variables
add_constrained_variable
add_constrained_variables
```

List of attributes associated with variables. [category AbstractVariableAttribute]
Expand Down
3 changes: 0 additions & 3 deletions src/Test/UnitTests/basic_constraint_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const BasicConstraintTests = Dict(

(MOI.VectorOfVariables, MOI.SOS1{Float64}) => ( dummy_vectorofvariables, 2, MOI.SOS1([1.0, 2.0]) ),
(MOI.VectorOfVariables, MOI.SOS2{Float64}) => ( dummy_vectorofvariables, 2, MOI.SOS2([1.0, 2.0]) ),
(MOI.VectorOfVariables, MOI.Reals) => ( dummy_vectorofvariables, 2, MOI.Reals(2) ),
(MOI.VectorOfVariables, MOI.Zeros) => ( dummy_vectorofvariables, 2, MOI.Zeros(2) ),
(MOI.VectorOfVariables, MOI.Nonpositives) => ( dummy_vectorofvariables, 2, MOI.Nonpositives(2) ),
(MOI.VectorOfVariables, MOI.Nonnegatives) => ( dummy_vectorofvariables, 2, MOI.Nonnegatives(2) ),
Expand Down Expand Up @@ -60,15 +59,13 @@ const BasicConstraintTests = Dict(
(MOI.ScalarQuadraticFunction{Float64}, MOI.EqualTo{Float64}) => ( dummy_scalar_quadratic, 1, MOI.EqualTo(1.0) ),
(MOI.ScalarQuadraticFunction{Float64}, MOI.Interval{Float64}) => ( dummy_scalar_quadratic, 1, MOI.Interval(1.0, 2.0) ),

(MOI.VectorAffineFunction{Float64}, MOI.Reals) => ( dummy_vector_affine, 2, MOI.Reals(2) ),
(MOI.VectorAffineFunction{Float64}, MOI.Zeros) => ( dummy_vector_affine, 2, MOI.Zeros(2) ),
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_affine, 2, MOI.Nonpositives(2) ),
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_affine, 2, MOI.Nonnegatives(2) ),

(MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => ( dummy_vector_affine, 3, MOI.SecondOrderCone(3) ),
(MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => ( dummy_vector_affine, 3, MOI.RotatedSecondOrderCone(3) ),

(MOI.VectorQuadraticFunction{Float64}, MOI.Reals) => ( dummy_vector_quadratic, 2, MOI.Reals(2) ),
(MOI.VectorQuadraticFunction{Float64}, MOI.Zeros) => ( dummy_vector_quadratic, 2, MOI.Zeros(2) ),
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_quadratic, 2, MOI.Nonpositives(2) ),
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_quadratic, 2, MOI.Nonnegatives(2) ),
Expand Down
22 changes: 20 additions & 2 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,28 @@ is, `copy_to(model, src)` does not throw [`UnsupportedConstraint`](@ref) when
`src` contains `F`-in-`S` constraints. If `F`-in-`S` constraints are only not
supported in specific circumstances, e.g. `F`-in-`S` constraints cannot be
combined with another type of constraint, it should still return `true`.
"""

supports_constraint(model::ModelLike, ::Type{VectorOfVariables}, ::Type{Reals})::Bool

Return a `Bool` indicating whether `model` supports free variables. That is,
`copy_to(model, src)` does not error when `src` contains variables that are not
constrained by any [`SingleVariable`](@ref) or [`VectorOfVariables`](@ref)
constraint. By default, this method returns `true` so it should only be
implemented if `model` does not support free variables. For instance, if a
solver requires all variables to be nonnegative, it should implement this
method and return `false` because free variables cannot be copied to the solver.

Note that free variables are not explicitly set to be free by calling
[`add_constraint`](@ref) with the set [`Reals`](@ref), instead, free variables
are created with [`add_variable`](@ref) and [`add_variables`](@ref).
If `model` does not support free variables, it should not implement
[`add_variable`](@ref) nor [`add_variables`](@ref) but should implement
this method and return `false`. This allows free variables to be bridged as the
sum of a nonnegative and a nonpositive variables.
""" # Implemented as only one method to avoid ambiguity
function supports_constraint(model::ModelLike, F::Type{<:AbstractFunction},
S::Type{<:AbstractSet})
return false
return F == VectorOfVariables && S == Reals
end

"""
Expand Down
67 changes: 67 additions & 0 deletions src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,70 @@ A [`AddVariableNotAllowed`](@ref) error is thrown if adding variables cannot be
done in the current state of the model `model`.
"""
add_variable(model::ModelLike) = throw(AddVariableNotAllowed())

"""
add_constrained_variable(
model::ModelLike,
set::AbstractScalarSet
)::Tuple{MOI.VariableIndex,
MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}}

Add to `model` a scalar variable constrained to belong to `set`, returning the
index of the variable created and the index of the constraint constraining the
variable to belong to `set`.

By default, this function falls back to creating a free variable with
[`add_variable`](@ref) and then constraining it to belong to `set` with
[`add_constraint`](@ref).
"""
function add_constrained_variable(model::ModelLike, set::AbstractScalarSet)
variable = add_variable(model)
constraint = add_constraint(model, SingleVariable(variable), set)
return variable, constraint
end

"""
add_constrained_variables(
model::ModelLike,
sets:AbstractVector{<:AbstractScalarSet}
)::Tuple{Vector{MOI.VariableIndex},
Vector{MOI.ConstraintIndex{MOI.SingleVariable, eltype(sets)}}}

Add to `model` scalar variables constrained to belong to `sets`, returning the
indices of the variables created and the indices of the constraints constraining
the variables to belong to each set in `sets`. That is, if it returns `variables`
and `constraints`, `constraints[i]` is the index of the constraint constraining
`variable[i]` to belong to `sets[i]`.

By default, this function falls back to calling
[`add_constrained_variable`](@ref) on each set.
"""
function add_constrained_variables(model::ModelLike, sets::AbstractVector{<:AbstractScalarSet})
variables = Vector{VariableIndex}(undef, length(sets))
constraints = Vector{ConstraintIndex{SingleVariable, eltype(sets)}}(undef, length(sets))
for (i, set) in enumerate(sets)
variables[i], constraints[i] = add_constrained_variable(model, set)
end
return variables, constraints
end

"""
add_constrained_variables(
model::ModelLike,
set::AbstractVectorSet
)::Tuple{Vector{MOI.VariableIndex},
MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}}

Add to `model` a vector of variables constrained to belong to `set`, returning
the indices of the variables created and the index of the constraint
constraining the vector of variables to belong to `set`.

By default, this function falls back to creating free variables with
[`add_variables`](@ref) and then constraining it to belong to `set` with
[`add_constraint`](@ref).
"""
function add_constrained_variables(model::ModelLike, set::AbstractVectorSet)
variables = add_variables(model, dimension(set))
constraint = add_constraint(model, VectorOfVariables(variables), set)
return variables, constraint
end