Skip to content

Commit 625fcc5

Browse files
authored
Merge pull request #796 from JuliaOpt/bl/add_constrained_variable
Define add_constrained_variable
2 parents deec96a + f791021 commit 625fcc5

File tree

5 files changed

+132
-8
lines changed

5 files changed

+132
-8
lines changed

docs/src/apimanual.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,9 @@ If ``\mathcal{C}_i`` is a vector set, the discussion remains valid with
929929
``y_i(\frac{1}{2}x^TQ_ix + a_i^T x + b_i)`` replaced with the scalar product
930930
between `y_i` and the vector of scalar-valued quadratic functions.
931931

932-
### Constraint bridges
932+
### Automatic reformulation
933+
934+
#### Constraint reformulation
933935

934936
A constraint often possess different equivalent formulations, but a solver may only support one of them.
935937
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.
@@ -966,6 +968,28 @@ model = MyPackage.Optimizer()
966968
MOI.set(model, MyPackage.PrintLevel(), 0)
967969
```
968970

971+
### Supported constrained variables and constraints
972+
973+
The solver interface should only implement support for constrained variables
974+
(see [`add_constrained_variable`](@ref)/[`add_constrained_variables`](@ref))
975+
or constraints that directly map to a structure exploited by the solver
976+
algorithm. There is no need to add support for additional types, this is
977+
handled by the [Automatic reformulation](@ref). Furthermore, this allows
978+
[`supports_constraint`](@ref) to indicate which types are exploited by the
979+
solver and hence allows layers such as [`Bridges.LazyBridgeOptimizer`](@ref)
980+
to accurately select the most appropriate transformations.
981+
982+
As [`add_constrained_variable`](@ref) (resp. [`add_constrained_variables`](@ref))
983+
falls back to [`add_variable`](@ref) (resp. [`add_variables`](@ref)) followed by
984+
[`add_constraint`](@ref), there is no need to implement this function
985+
if `model` supports creating free variables. However, if `model` does not
986+
support creating free variables, then it should only implement
987+
[`add_constrained_variable`](@ref) and not [`add_variable`](@ref) nor
988+
[`add_constraint`](@ref) for [`SingleVariable`](@ref)-in-`typeof(set)`.
989+
In addition, it should implement `supports_constraint(::Optimizer,
990+
::Type{VectorOfVariables}, ::Type{Reals})` and return `false` so that free
991+
variables are bridged, see [`supports_constraint`](@ref).
992+
969993
### Implementing copy
970994

971995
Avoid storing extra copies of the problem when possible. This means that solver

docs/src/apireference.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,29 @@ delete(::ModelLike, ::Index)
182182

183183
### Variables
184184

185-
Functions for adding variables. For deleting, see index types section.
185+
*Free variables* are the variables created with [`add_variable`](@ref) or
186+
[`add_variables`](@ref) while *constrained variables* are
187+
the variables created with [`add_constrained_variable`](@ref) or
188+
[`add_constrained_variables`](@ref). Adding constrained variables instead of
189+
constraining free variables with [`add_constraint`](@ref) allows Variable
190+
bridges to be used.
191+
Note further that free variables that are constrained with
192+
[`add_constraint`](@ref) may be copied by [`copy_to`](@ref) with
193+
[`add_constrained_variable`](@ref) or [`add_constrained_variables`](@ref) by the
194+
[`Utilities.CachingOptimizer`](@ref).
195+
More precisely, the attributes do not distinguish constraints on variables
196+
created with `add_constrained_variable(s)` or `add_variable(s)`/`add_constraint`.
197+
When the model is copied, if a variable is constrained in several sets,
198+
the implementation of [`copy_to`](@ref) can determine whether it is added
199+
using [`add_variable`](@ref) or [`add_constrained_variable`](@ref) with one
200+
of the sets. The rest of the constraints on the variables are added
201+
with [`add_constraint`](@ref). For deleting, see [Index types](@ref).
186202

187203
```@docs
188-
add_variables
189204
add_variable
205+
add_variables
206+
add_constrained_variable
207+
add_constrained_variables
190208
```
191209

192210
List of attributes associated with variables. [category AbstractVariableAttribute]

src/Test/UnitTests/basic_constraint_tests.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const BasicConstraintTests = Dict(
3030

3131
(MOI.VectorOfVariables, MOI.SOS1{Float64}) => ( dummy_vectorofvariables, 2, MOI.SOS1([1.0, 2.0]) ),
3232
(MOI.VectorOfVariables, MOI.SOS2{Float64}) => ( dummy_vectorofvariables, 2, MOI.SOS2([1.0, 2.0]) ),
33-
(MOI.VectorOfVariables, MOI.Reals) => ( dummy_vectorofvariables, 2, MOI.Reals(2) ),
3433
(MOI.VectorOfVariables, MOI.Zeros) => ( dummy_vectorofvariables, 2, MOI.Zeros(2) ),
3534
(MOI.VectorOfVariables, MOI.Nonpositives) => ( dummy_vectorofvariables, 2, MOI.Nonpositives(2) ),
3635
(MOI.VectorOfVariables, MOI.Nonnegatives) => ( dummy_vectorofvariables, 2, MOI.Nonnegatives(2) ),
@@ -60,15 +59,13 @@ const BasicConstraintTests = Dict(
6059
(MOI.ScalarQuadraticFunction{Float64}, MOI.EqualTo{Float64}) => ( dummy_scalar_quadratic, 1, MOI.EqualTo(1.0) ),
6160
(MOI.ScalarQuadraticFunction{Float64}, MOI.Interval{Float64}) => ( dummy_scalar_quadratic, 1, MOI.Interval(1.0, 2.0) ),
6261

63-
(MOI.VectorAffineFunction{Float64}, MOI.Reals) => ( dummy_vector_affine, 2, MOI.Reals(2) ),
6462
(MOI.VectorAffineFunction{Float64}, MOI.Zeros) => ( dummy_vector_affine, 2, MOI.Zeros(2) ),
6563
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_affine, 2, MOI.Nonpositives(2) ),
6664
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_affine, 2, MOI.Nonnegatives(2) ),
6765

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

71-
(MOI.VectorQuadraticFunction{Float64}, MOI.Reals) => ( dummy_vector_quadratic, 2, MOI.Reals(2) ),
7269
(MOI.VectorQuadraticFunction{Float64}, MOI.Zeros) => ( dummy_vector_quadratic, 2, MOI.Zeros(2) ),
7370
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_quadratic, 2, MOI.Nonpositives(2) ),
7471
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_quadratic, 2, MOI.Nonnegatives(2) ),

src/constraints.jl

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,28 @@ is, `copy_to(model, src)` does not throw [`UnsupportedConstraint`](@ref) when
88
`src` contains `F`-in-`S` constraints. If `F`-in-`S` constraints are only not
99
supported in specific circumstances, e.g. `F`-in-`S` constraints cannot be
1010
combined with another type of constraint, it should still return `true`.
11-
"""
11+
12+
supports_constraint(model::ModelLike, ::Type{VectorOfVariables}, ::Type{Reals})::Bool
13+
14+
Return a `Bool` indicating whether `model` supports free variables. That is,
15+
`copy_to(model, src)` does not error when `src` contains variables that are not
16+
constrained by any [`SingleVariable`](@ref) or [`VectorOfVariables`](@ref)
17+
constraint. By default, this method returns `true` so it should only be
18+
implemented if `model` does not support free variables. For instance, if a
19+
solver requires all variables to be nonnegative, it should implement this
20+
method and return `false` because free variables cannot be copied to the solver.
21+
22+
Note that free variables are not explicitly set to be free by calling
23+
[`add_constraint`](@ref) with the set [`Reals`](@ref), instead, free variables
24+
are created with [`add_variable`](@ref) and [`add_variables`](@ref).
25+
If `model` does not support free variables, it should not implement
26+
[`add_variable`](@ref) nor [`add_variables`](@ref) but should implement
27+
this method and return `false`. This allows free variables to be bridged as the
28+
sum of a nonnegative and a nonpositive variables.
29+
""" # Implemented as only one method to avoid ambiguity
1230
function supports_constraint(model::ModelLike, F::Type{<:AbstractFunction},
1331
S::Type{<:AbstractSet})
14-
return false
32+
return F == VectorOfVariables && S == Reals
1533
end
1634

1735
"""

src/variables.jl

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,70 @@ A [`AddVariableNotAllowed`](@ref) error is thrown if adding variables cannot be
3333
done in the current state of the model `model`.
3434
"""
3535
add_variable(model::ModelLike) = throw(AddVariableNotAllowed())
36+
37+
"""
38+
add_constrained_variable(
39+
model::ModelLike,
40+
set::AbstractScalarSet
41+
)::Tuple{MOI.VariableIndex,
42+
MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}}
43+
44+
Add to `model` a scalar variable constrained to belong to `set`, returning the
45+
index of the variable created and the index of the constraint constraining the
46+
variable to belong to `set`.
47+
48+
By default, this function falls back to creating a free variable with
49+
[`add_variable`](@ref) and then constraining it to belong to `set` with
50+
[`add_constraint`](@ref).
51+
"""
52+
function add_constrained_variable(model::ModelLike, set::AbstractScalarSet)
53+
variable = add_variable(model)
54+
constraint = add_constraint(model, SingleVariable(variable), set)
55+
return variable, constraint
56+
end
57+
58+
"""
59+
add_constrained_variables(
60+
model::ModelLike,
61+
sets:AbstractVector{<:AbstractScalarSet}
62+
)::Tuple{Vector{MOI.VariableIndex},
63+
Vector{MOI.ConstraintIndex{MOI.SingleVariable, eltype(sets)}}}
64+
65+
Add to `model` scalar variables constrained to belong to `sets`, returning the
66+
indices of the variables created and the indices of the constraints constraining
67+
the variables to belong to each set in `sets`. That is, if it returns `variables`
68+
and `constraints`, `constraints[i]` is the index of the constraint constraining
69+
`variable[i]` to belong to `sets[i]`.
70+
71+
By default, this function falls back to calling
72+
[`add_constrained_variable`](@ref) on each set.
73+
"""
74+
function add_constrained_variables(model::ModelLike, sets::AbstractVector{<:AbstractScalarSet})
75+
variables = Vector{VariableIndex}(undef, length(sets))
76+
constraints = Vector{ConstraintIndex{SingleVariable, eltype(sets)}}(undef, length(sets))
77+
for (i, set) in enumerate(sets)
78+
variables[i], constraints[i] = add_constrained_variable(model, set)
79+
end
80+
return variables, constraints
81+
end
82+
83+
"""
84+
add_constrained_variables(
85+
model::ModelLike,
86+
set::AbstractVectorSet
87+
)::Tuple{Vector{MOI.VariableIndex},
88+
MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}}
89+
90+
Add to `model` a vector of variables constrained to belong to `set`, returning
91+
the indices of the variables created and the index of the constraint
92+
constraining the vector of variables to belong to `set`.
93+
94+
By default, this function falls back to creating free variables with
95+
[`add_variables`](@ref) and then constraining it to belong to `set` with
96+
[`add_constraint`](@ref).
97+
"""
98+
function add_constrained_variables(model::ModelLike, set::AbstractVectorSet)
99+
variables = add_variables(model, dimension(set))
100+
constraint = add_constraint(model, VectorOfVariables(variables), set)
101+
return variables, constraint
102+
end

0 commit comments

Comments
 (0)