Skip to content

Commit 061f093

Browse files
committed
Add supports_constrained_variable(s)
1 parent 49180c6 commit 061f093

File tree

6 files changed

+103
-15
lines changed

6 files changed

+103
-15
lines changed

docs/src/apimanual.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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_constrained_variable(bridged_optimizer, MOI.GreaterThan{Float64})
10561056
10571057
# output
10581058
@@ -1143,9 +1143,9 @@ if `model` supports creating free variables. However, if `model` does not
11431143
support creating free variables, then it should only implement
11441144
[`add_constrained_variable`](@ref) and not [`add_variable`](@ref) nor
11451145
[`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).
1146+
In addition, it should implement `supports_constrained_variables(::Optimizer,
1147+
::Type{Reals})` and return `false` so that free variables are bridged,
1148+
see [`supports_constrained_variables`](@ref).
11491149

11501150
### Handling duplicate coefficients
11511151

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_constrained_variable
228+
supports_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_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_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_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_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_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_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_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 removed this condition, as `supports_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_constrained_variables(model, Reals)
37+
else
38+
return false
39+
end
3340
end
3441

3542
"""

src/variables.jl

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

37+
"""
38+
supports_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_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 decide 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, it will transparently be bridged
62+
into a supported constraint.
63+
"""
64+
function supports_constrained_variable(model::ModelLike,
65+
S::Type{<:AbstractScalarSet})
66+
return supports_constrained_variables(model, Reals) &&
67+
supports_constraint(model, SingleVariable, S)
68+
end
69+
3770
"""
3871
add_constrained_variable(
3972
model::ModelLike,
@@ -55,6 +88,49 @@ function add_constrained_variable(model::ModelLike, set::AbstractScalarSet)
5588
return variable, constraint
5689
end
5790

91+
"""
92+
supports_constrained_variables(
93+
model::ModelLike,
94+
S::Type{<:AbstractScalarSet}
95+
)::Bool
96+
97+
Return a `Bool` indicating whether `model` supports constraining a vector of
98+
variables to belong to a set of type `S` either on creation of the vector of
99+
variables with [`add_constrained_variables`](@ref) or after the variable is
100+
created with [`add_constraint`](@ref).
101+
102+
By default, if `S` is `Reals` then this function returns `true` and otherwise,
103+
it falls back to `supports_constrained_variables(model, Reals) &&
104+
supports_constraint(model, MOI.VectorOfVariables, S)` which is the correct
105+
definition for most models.
106+
107+
## Example
108+
109+
In the standard conic form, the variables are grouped into several cones
110+
and the constraints are affine equality constraints.
111+
If `Reals` is not one of the cones supported by the solvers then it needs
112+
to implement `supports_constrained_variables(::Optimizer, ::Type{Reals}) = false`
113+
as free variables are not supported.
114+
The solvers should then implement
115+
`supports_constrained_variables(::Optimizer, ::Type{<:SupportedCones}) = true`
116+
where `SupportedCones` is the union of all cone types that are supported
117+
but it should not implement
118+
`supports_constraint(::Type{VectorOfVariables}, Type{<:SupportedCones})`
119+
as it should return `false`.
120+
This prevents the user to constrain the same variable in two different cones.
121+
When a `VectorOfVariables`-in-`S` is added, the variables of the vector
122+
have already been created so they already belong to given cones.
123+
The constraint will therefore be bridged by adding slack variables in `S`
124+
and equality constraints ensuring that the slack variables are equal to the
125+
corresponding variables of the given constraint function.
126+
"""
127+
function supports_constrained_variables(model::ModelLike,
128+
S::Type{<:AbstractVectorSet})
129+
return supports_constrained_variables(model, Reals) &&
130+
supports_constraint(model, VectorOfVariables, S)
131+
end
132+
supports_constrained_variables(::ModelLike, ::Type{Reals}) = true
133+
58134
"""
59135
add_constrained_variables(
60136
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_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_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_constrained_variable(model, S)
7376
end
7477
end
7578
end

0 commit comments

Comments
 (0)