Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Comment thread
blegat marked this conversation as resolved.
(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
25 changes: 23 additions & 2 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,32 @@ 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) and never constrained while *constrained variables* are
the variables either created with [`add_constrained_variable`](@ref) or
[`add_constrained_variables`](@ref) or created with [`add_variable`](@ref) or
[`add_variables`](@ref) and constrained after being created using
Comment thread
blegat marked this conversation as resolved.
Outdated
[`add_constraint`](@ref) with the [`SingleVariable`](@ref) or
[`VectorOfVariables`](@ref). However, they need to be added with
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) to
allow Variable bridges to be used.
Note further that variables that are constrained after being created using
[`add_constraint`](@ref) may be copied by [`copy_to`](@ref) with
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) by the
Comment thread
blegat marked this conversation as resolved.
[`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` free variables cannot be copied to the solver.
Comment thread
blegat marked this conversation as resolved.
Outdated

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
69 changes: 69 additions & 0 deletions src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,72 @@ 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), which turns the free variable into a constrained
Comment thread
blegat marked this conversation as resolved.
Outdated
variable.
"""
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), which turns the free variables into constrained
variables.
"""
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