Skip to content

Commit 68c2882

Browse files
authored
Merge pull request #1004 from JuliaOpt/bl/supports_constrained_variable
Add supports_constrained_variable(s)
2 parents 823ffc6 + 9cb596f commit 68c2882

File tree

6 files changed

+131
-28
lines changed

6 files changed

+131
-28
lines changed

docs/src/apimanual.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ read!(io, src_2)
864864
### Duals
865865

866866
Conic duality is the starting point for MOI's duality conventions. When all functions are affine (or coordinate projections), and all constraint sets are closed convex cones, the model may be called a conic optimization problem.
867-
For conic-form minimization problems, the primal is:
867+
For a minimization problem in geometric conic form, the primal is:
868868

869869
```math
870870
\begin{align}
@@ -874,7 +874,7 @@ For conic-form minimization problems, the primal is:
874874
\end{align}
875875
```
876876

877-
and the dual is:
877+
and the dual is a maximization problem in standard conic form:
878878

879879
```math
880880
\begin{align}
@@ -888,7 +888,7 @@ and the dual is:
888888

889889
where each ``\mathcal{C}_i`` is a closed convex cone and ``\mathcal{C}_i^*`` is its dual cone.
890890

891-
For conic-form maximization problems, the primal is:
891+
For a maximization problem in geometric conic form, the primal is:
892892
```math
893893
\begin{align}
894894
& \max_{x \in \mathbb{R}^n} & a_0^T x + b_0
@@ -897,7 +897,7 @@ For conic-form maximization problems, the primal is:
897897
\end{align}
898898
```
899899

900-
and the dual is:
900+
and the dual is a minimization problem in standard conic form:
901901

902902
```math
903903
\begin{align}
@@ -1052,7 +1052,7 @@ The `Bridges.Variable.Vectorize` is the bridge optimizer that applies the
10521052
the optimizer
10531053
```jldoctest; setup=:(optimizer = MOI.Utilities.Model{Float64}())
10541054
bridged_optimizer = MOI.Bridges.Variable.Vectorize{Float64}(optimizer)
1055-
MOI.supports_constraint(bridged_optimizer, MOI.SingleVariable, MOI.GreaterThan{Float64})
1055+
MOI.supports_add_constrained_variable(bridged_optimizer, MOI.GreaterThan{Float64})
10561056
10571057
# output
10581058
@@ -1127,25 +1127,27 @@ MOI.set(model, MyPackage.PrintLevel(), 0)
11271127

11281128
### Supported constrained variables and constraints
11291129

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

11391140
As [`add_constrained_variable`](@ref) (resp. [`add_constrained_variables`](@ref))
11401141
falls back to [`add_variable`](@ref) (resp. [`add_variables`](@ref)) followed by
11411142
[`add_constraint`](@ref), there is no need to implement this function
1142-
if `model` supports creating free variables. However, if `model` does not
1143-
support creating free variables, then it should only implement
1144-
[`add_constrained_variable`](@ref) and not [`add_variable`](@ref) nor
1145-
[`add_constraint`](@ref) for [`SingleVariable`](@ref)-in-`typeof(set)`.
1146-
In addition, it should implement `supports_constraint(::Optimizer,
1147-
::Type{VectorOfVariables}, ::Type{Reals})` and return `false` so that free
1148-
variables are bridged, see [`supports_constraint`](@ref).
1143+
if `model` does not require that variables be constrained when they are created.
1144+
However, if `model` requires that variables be constrained when they're created,
1145+
then it should only implement [`add_constrained_variable`](@ref) and not
1146+
[`add_variable`](@ref) nor [`add_constraint`](@ref) for
1147+
[`SingleVariable`](@ref)-in-`typeof(set)`. In addition, it should implement
1148+
`supports_add_constrained_variables(::Optimizer, ::Type{Reals})` and return
1149+
`false` so that these variables are bridged, see
1150+
[`supports_add_constrained_variables`](@ref).
11491151

11501152
### Handling duplicate coefficients
11511153

docs/src/apireference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ add_variable
224224
add_variables
225225
add_constrained_variable
226226
add_constrained_variables
227+
supports_add_constrained_variable
228+
supports_add_constrained_variables
227229
```
228230

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

src/Test/contconic.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ function _lin2test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
103103
@test MOI.supports(model, MOI.ObjectiveSense())
104104
if vecofvars
105105
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonnegatives)
106-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonpositives)
106+
@test MOI.supports_add_constrained_variables(model, MOI.Nonpositives)
107107
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros)
108108
else
109109
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives)
@@ -258,7 +258,7 @@ function lin4test(model::MOI.ModelLike, config::TestConfig)
258258
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
259259
@test MOI.supports(model, MOI.ObjectiveSense())
260260
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives)
261-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonpositives)
261+
@test MOI.supports_add_constrained_variables(model, MOI.Nonpositives)
262262

263263
MOI.empty!(model)
264264
@test MOI.is_empty(model)
@@ -586,11 +586,11 @@ function _soc1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
586586
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
587587
@test MOI.supports(model, MOI.ObjectiveSense())
588588
if vecofvars
589-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros)
589+
@test MOI.supports_add_constrained_variables(model, MOI.SecondOrderCone)
590590
else
591-
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros)
591+
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone)
592592
end
593-
@test MOI.supports_constraint(model, MOI.VectorOfVariables,MOI.SecondOrderCone)
593+
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros)
594594

595595
MOI.empty!(model)
596596
@test MOI.is_empty(model)
@@ -875,7 +875,7 @@ function _rotatedsoc1test(model::MOI.ModelLike, config::TestConfig, abvars::Bool
875875
@test MOI.supports(model, MOI.ObjectiveSense())
876876
if abvars
877877
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{Float64})
878-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone)
878+
@test MOI.supports_add_constrained_variables(model, MOI.RotatedSecondOrderCone)
879879
else
880880
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone)
881881
end
@@ -977,7 +977,7 @@ function rotatedsoc2test(model::MOI.ModelLike, config::TestConfig)
977977
@test MOI.supports_constraint(model, MOI.SingleVariable,MOI.EqualTo{Float64})
978978
@test MOI.supports_constraint(model, MOI.SingleVariable,MOI.LessThan{Float64})
979979
@test MOI.supports_constraint(model, MOI.SingleVariable,MOI.GreaterThan{Float64})
980-
@test MOI.supports_constraint(model, MOI.VectorOfVariables,MOI.RotatedSecondOrderCone)
980+
@test MOI.supports_add_constrained_variables(model, MOI.RotatedSecondOrderCone)
981981

982982
MOI.empty!(model)
983983
@test MOI.is_empty(model)
@@ -1045,7 +1045,7 @@ function rotatedsoc3test(model::MOI.ModelLike, config::TestConfig; n=2, ub=3.0)
10451045
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
10461046
@test MOI.supports(model, MOI.ObjectiveSense())
10471047
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{Float64})
1048-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonnegatives)
1048+
@test MOI.supports_add_constrained_variables(model, MOI.Nonnegatives)
10491049
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{Float64})
10501050
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{Float64})
10511051
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone)
@@ -1152,7 +1152,7 @@ function rotatedsoc4test(model::MOI.ModelLike, config::TestConfig; n=2, ub=3.0)
11521152
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
11531153
@test MOI.supports(model, MOI.ObjectiveSense())
11541154
@test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
1155-
@test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone)
1155+
@test MOI.supports_add_constrained_variables(model, MOI.RotatedSecondOrderCone)
11561156

11571157
MOI.empty!(model)
11581158
@test MOI.is_empty(model)

src/constraints.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,16 @@ If `model` does not support free variables, it should not implement
2727
this method and return `false`. This allows free variables to be bridged as the
2828
sum of a nonnegative and a nonpositive variables.
2929
""" # Implemented as only one method to avoid ambiguity
30-
function supports_constraint(::ModelLike, F::Type{<:AbstractFunction},
30+
function supports_constraint(model::ModelLike, F::Type{<:AbstractFunction},
3131
S::Type{<:AbstractSet})
32-
return F === VectorOfVariables && S === Reals
32+
# TODO remove this condition, as `supports_add_constrained_variables(model, Reals)`
33+
# should be called instead of
34+
# `supports_constraint(model, ::VectorOfVariables, ::Reals)
35+
if F === VectorOfVariables && S === Reals
36+
return supports_add_constrained_variables(model, Reals)
37+
else
38+
return false
39+
end
3340
end
3441

3542
"""

src/variables.jl

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ done in the current state of the model `model`.
3434
"""
3535
add_variable(model::ModelLike) = throw(AddVariableNotAllowed())
3636

37+
"""
38+
supports_add_constrained_variable(
39+
model::ModelLike,
40+
S::Type{<:AbstractScalarSet}
41+
)::Bool
42+
43+
Return a `Bool` indicating whether `model` supports constraining a variable
44+
to belong to a set of type `S` either on creation of the variable with
45+
[`add_constrained_variable`](@ref) or after the variable is created with
46+
[`add_constraint`](@ref).
47+
48+
By default, this function falls back to
49+
`supports_add_constrained_variables(model, Reals) &&
50+
supports_constraint(model, MOI.SingleVariable, S)` which is the correct
51+
definition for most models.
52+
53+
## Example
54+
55+
Suppose that a solver supports only two kind of variables: binary variables
56+
and continuous variables with a lower bound. If the solver decides not to
57+
support `SingleVariable`-in-`Binary` and `SingleVariable`-in-`GreaterThan`
58+
constraints, it only has to implement `add_constrained_variable` for these
59+
two sets which prevents the user to add both a binary constraint and a
60+
lower bound on the same variable. Moreover, if the user adds a
61+
`SingleVariable`-in-`GreaterThan` constraint, implementing this interface (i.e.,
62+
`supports_add_constrained_variables`) enables the constraint to be transparently
63+
bridged into a supported constraint.
64+
"""
65+
function supports_add_constrained_variable(model::ModelLike,
66+
S::Type{<:AbstractScalarSet})
67+
return supports_add_constrained_variables(model, Reals) &&
68+
supports_constraint(model, SingleVariable, S)
69+
end
70+
3771
"""
3872
add_constrained_variable(
3973
model::ModelLike,
@@ -55,6 +89,61 @@ function add_constrained_variable(model::ModelLike, set::AbstractScalarSet)
5589
return variable, constraint
5690
end
5791

92+
"""
93+
supports_add_constrained_variables(
94+
model::ModelLike,
95+
S::Type{<:AbstractScalarSet}
96+
)::Bool
97+
98+
Return a `Bool` indicating whether `model` supports constraining a vector of
99+
variables to belong to a set of type `S` either on creation of the vector of
100+
variables with [`add_constrained_variables`](@ref) or after the variable is
101+
created with [`add_constraint`](@ref).
102+
103+
By default, if `S` is `Reals` then this function returns `true` and otherwise,
104+
it falls back to `supports_add_constrained_variables(model, Reals) &&
105+
supports_constraint(model, MOI.VectorOfVariables, S)` which is the correct
106+
definition for most models.
107+
108+
## Example
109+
110+
In the standard conic form (see [Duals](@ref)), the variables are grouped into
111+
several cones and the constraints are affine equality constraints.
112+
If `Reals` is not one of the cones supported by the solvers then it needs
113+
to implement `supports_add_constrained_variables(::Optimizer, ::Type{Reals}) = false`
114+
as free variables are not supported.
115+
The solvers should then implement
116+
`supports_add_constrained_variables(::Optimizer, ::Type{<:SupportedCones}) = true`
117+
where `SupportedCones` is the union of all cone types that are supported;
118+
it does not have to implement the method
119+
`supports_constraint(::Type{VectorOfVariables}, Type{<:SupportedCones})`
120+
as it should return `false` and it's the default.
121+
This prevents the user to constrain the same variable in two different cones.
122+
When a `VectorOfVariables`-in-`S` is added, the variables of the vector
123+
have already been created so they already belong to given cones.
124+
If bridges are enabled, the constraint will therefore be bridged by adding slack
125+
variables in `S` and equality constraints ensuring that the slack variables are
126+
equal to the corresponding variables of the given constraint function.
127+
128+
Note that there may also be sets for which
129+
`!supports_add_constrained_variables(model, S)` and
130+
`supports_constraint(model, MOI.VectorOfVariables, S)`.
131+
For instance, suppose a solver supports positive semidefinite variable
132+
constraints and two types of variables: binary variables and nonnegative
133+
variables. Then the solver should support adding
134+
`VectorOfVariables`-in-`PositiveSemidefiniteConeTriangle` constraints, but it
135+
should not support creating variables constrained to belong to the
136+
`PositiveSemidefiniteConeTriangle` because the variables in
137+
`PositiveSemidefiniteConeTriangle` should first be created as either binary or
138+
non-negative.
139+
"""
140+
function supports_add_constrained_variables(
141+
model::ModelLike, S::Type{<:AbstractVectorSet})
142+
return supports_add_constrained_variables(model, Reals) &&
143+
supports_constraint(model, VectorOfVariables, S)
144+
end
145+
supports_add_constrained_variables(::ModelLike, ::Type{Reals}) = true
146+
58147
"""
59148
add_constrained_variables(
60149
model::ModelLike,

test/Utilities/model.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,18 @@ struct DummySet <: MOI.AbstractScalarSet end
6161
@testset "`SingleVariable`-in-`S`" begin
6262
@test !MOI.supports_constraint(model, DummyFunction, DummySet)
6363
@test !MOI.supports_constraint(model, MOI.SingleVariable, DummySet)
64+
@test !MOI.supports_add_constrained_variable(model, DummySet)
6465
for S in [MOI.EqualTo{T}, MOI.GreaterThan{T}, MOI.LessThan{T},
6566
MOI.Interval{T}, MOI.Integer, MOI.ZeroOne,
6667
MOI.Semicontinuous{T}, MOI.Semiinteger{T}]
6768
@test MOI.supports_constraint(model, MOI.SingleVariable, S)
69+
@test MOI.supports_add_constrained_variable(model, S)
6870
end
6971
U = Float32
7072
for S in [MOI.EqualTo{U}, MOI.GreaterThan{U}, MOI.LessThan{U},
7173
MOI.Interval{U}, MOI.Semicontinuous{U}, MOI.Semiinteger{U}]
7274
@test !MOI.supports_constraint(model, MOI.SingleVariable, S)
75+
@test !MOI.supports_add_constrained_variable(model, S)
7376
end
7477
end
7578
end

0 commit comments

Comments
 (0)