-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathconstraints.jl
314 lines (274 loc) · 9.97 KB
/
constraints.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
"""
supports_constraint(
model::ModelLike,
::Type{F},
::Type{S},
)::Bool where {F<:AbstractFunction,S<:AbstractSet}
Return a `Bool` indicating whether `model` supports `F`-in-`S` constraints, that
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`.
"""
function supports_constraint(
::ModelLike,
::Type{<:AbstractFunction},
::Type{<:AbstractSet},
)
return false
end
"""
struct UnsupportedConstraint{F<:AbstractFunction, S<:AbstractSet} <: UnsupportedError
message::String # Human-friendly explanation why the attribute cannot be set
end
An error indicating that constraints of type `F`-in-`S` are not supported by
the model, i.e. that [`supports_constraint`](@ref) returns `false`.
"""
struct UnsupportedConstraint{F<:AbstractFunction,S<:AbstractSet} <:
UnsupportedError
message::String # Human-friendly explanation why the attribute cannot be set
end
UnsupportedConstraint{F,S}() where {F,S} = UnsupportedConstraint{F,S}("")
function element_name(::UnsupportedConstraint{F,S}) where {F,S}
return "`$F`-in-`$S` constraint"
end
"""
struct AddConstraintNotAllowed{F<:AbstractFunction, S<:AbstractSet} <: NotAllowedError
message::String # Human-friendly explanation why the attribute cannot be set
end
An error indicating that constraints of type `F`-in-`S` are supported (see
[`supports_constraint`](@ref)) but cannot be added.
"""
struct AddConstraintNotAllowed{F<:AbstractFunction,S<:AbstractSet} <:
NotAllowedError
message::String # Human-friendly explanation why the attribute cannot be set
end
AddConstraintNotAllowed{F,S}() where {F,S} = AddConstraintNotAllowed{F,S}("")
function operation_name(::AddConstraintNotAllowed{F,S}) where {F,S}
return "Adding `$F`-in-`$S` constraints"
end
"""
struct ScalarFunctionConstantNotZero{T, F, S} <: Exception
constant::T
end
An error indicating that the constant part of the function in the constraint
`F`-in-`S` is nonzero.
"""
struct ScalarFunctionConstantNotZero{T,F,S} <: Exception
constant::T
end
function Base.showerror(
io::IO,
err::ScalarFunctionConstantNotZero{T,F,S},
) where {T,F,S}
return print(
io,
"In `$F`-in-`$S` constraint: Constant $(err.constant) of the ",
"function is not zero. The function constant should be moved to the ",
"set. You can use `MOI.Utilities.normalize_and_add_constraint` which does ",
"this automatically.",
)
end
"""
throw_if_scalar_and_constant_not_zero(func, S::Type)
Throw a `ScalarFunctionConstantNotZero(index)` error `func` is a scalar function
whose constant is not zero.
"""
function throw_if_scalar_and_constant_not_zero(
func::AbstractScalarFunction,
::Type{S},
) where {S<:AbstractScalarSet}
cst = constant(func)
if !iszero(cst)
throw(ScalarFunctionConstantNotZero{typeof(cst),typeof(func),S}(cst))
end
return
end
function throw_if_scalar_and_constant_not_zero(
::VariableIndex,
::Type{S},
) where {S<:AbstractScalarSet}
return
end
function throw_if_scalar_and_constant_not_zero(
::AbstractVectorFunction,
::Type{S},
) where {S<:AbstractVectorSet}
return
end
"""
add_constraint(model::ModelLike, func::F, set::S)::ConstraintIndex{F,S} where {F,S}
Add the constraint ``f(x) \\in \\mathcal{S}`` where ``f`` is defined by `func`, and ``\\mathcal{S}`` is defined by `set`.
add_constraint(model::ModelLike, v::VariableIndex, set::S)::ConstraintIndex{VariableIndex,S} where {S}
add_constraint(model::ModelLike, vec::Vector{VariableIndex}, set::S)::ConstraintIndex{VectorOfVariables,S} where {S}
Add the constraint ``v \\in \\mathcal{S}`` where ``v`` is the variable (or vector of variables) referenced by `v` and ``\\mathcal{S}`` is defined by `set`.
* An [`UnsupportedConstraint`](@ref) error is thrown if `model` does not support
`F`-in-`S` constraints,
* a [`AddConstraintNotAllowed`](@ref) error is thrown if it supports `F`-in-`S`
constraints but it cannot add the constraint(s) in its current state and
* a [`ScalarFunctionConstantNotZero`](@ref) error may be thrown if
`func` is an `AbstractScalarFunction` with nonzero constant and `set`
is [`EqualTo`](@ref), [`GreaterThan`](@ref), [`LessThan`](@ref) or
[`Interval`](@ref).
* a [`LowerBoundAlreadySet`](@ref) error is thrown if `F` is a
[`VariableIndex`](@ref) and a constraint was already added to this variable
that sets a lower bound.
* a [`UpperBoundAlreadySet`](@ref) error is thrown if `F` is a
[`VariableIndex`](@ref) and a constraint was already added to this variable
that sets an upper bound.
"""
function add_constraint(
model::ModelLike,
func::AbstractFunction,
set::AbstractSet,
)
return throw_add_constraint_error_fallback(model, func, set)
end
# throw_add_constraint_error_fallback checks whether func and set are both
# scalar or both vector. If it is the case, it calls
# `correct_throw_add_constraint_error_fallback`
function throw_add_constraint_error_fallback(
model::ModelLike,
func::AbstractScalarFunction,
set::AbstractScalarSet;
kwargs...,
)
return correct_throw_add_constraint_error_fallback(
model,
func,
set;
kwargs...,
)
end
function throw_add_constraint_error_fallback(
model::ModelLike,
func::AbstractVectorFunction,
set::AbstractVectorSet;
kwargs...,
)
return correct_throw_add_constraint_error_fallback(
model,
func,
set;
kwargs...,
)
end
function throw_add_constraint_error_fallback(
model::ModelLike,
func::AbstractScalarFunction,
set::AbstractVectorSet;
kwargs...,
)
return error(
"Cannot add a constraint of the form `ScalarFunction`-in-`VectorSet`",
)
end
function throw_add_constraint_error_fallback(
model::ModelLike,
func::AbstractVectorFunction,
set::AbstractScalarSet;
kwargs...,
)
return error(
"Cannot add a constraint of the form `VectorFunction`-in-`ScalarSet`",
)
end
# func and set are both scalar or both vector
function correct_throw_add_constraint_error_fallback(
model::ModelLike,
func::AbstractFunction,
set::AbstractSet;
error_if_supported = AddConstraintNotAllowed{typeof(func),typeof(set)}(),
)
if supports_constraint(model, typeof(func), typeof(set))
throw(error_if_supported)
else
throw(UnsupportedConstraint{typeof(func),typeof(set)}())
end
end
# TODO(odow): remove this?
function add_constraint(
model::ModelLike,
v::Vector{VariableIndex},
set::AbstractVectorSet,
)
return add_constraint(model, VectorOfVariables(v), set)
end
"""
add_constraints(model::ModelLike, funcs::Vector{F}, sets::Vector{S})::Vector{ConstraintIndex{F,S}} where {F,S}
Add the set of constraints specified by each function-set pair in `funcs` and `sets`. `F` and `S` should be concrete types.
This call is equivalent to `add_constraint.(model, funcs, sets)` but may be more efficient.
"""
function add_constraints end
# default fallback
function add_constraints(model::ModelLike, funcs, sets)
return add_constraint.(model, funcs, sets)
end
"""
LowerBoundAlreadySet{S1, S2}
Error thrown when setting a `VariableIndex`-in-`S2` when a
`VariableIndex`-in-`S1` has already been added and the sets `S1`, `S2` both
set a lower bound, i.e. they are [`EqualTo`](@ref), [`GreaterThan`](@ref),
[`Interval`](@ref), [`Semicontinuous`](@ref) or [`Semiinteger`](@ref).
"""
struct LowerBoundAlreadySet{S1,S2}
vi::VariableIndex
end
function Base.showerror(io::IO, err::LowerBoundAlreadySet{S1,S2}) where {S1,S2}
return print(
io,
typeof(err),
": Cannot add `VariableIndex`-in-`$(S2)` constraint for variable ",
"$(err.vi) as a `VariableIndex`-in-`$(S1)` constraint was already ",
"set for this variable and both constraints set a lower bound.",
)
end
"""
UpperBoundAlreadySet{S1, S2}
Error thrown when setting a `VariableIndex`-in-`S2` when a
`VariableIndex`-in-`S1` has already been added and the sets `S1`, `S2` both
set an upper bound, i.e. they are [`EqualTo`](@ref), [`LessThan`](@ref),
[`Interval`](@ref), [`Semicontinuous`](@ref) or [`Semiinteger`](@ref).
"""
struct UpperBoundAlreadySet{S1,S2}
vi::VariableIndex
end
function Base.showerror(io::IO, err::UpperBoundAlreadySet{S1,S2}) where {S1,S2}
return print(
io,
typeof(err),
": Cannot add `VariableIndex`-in-`$(S2)` constraint for variable ",
"$(err.vi) as a `VariableIndex`-in-`$(S1)` constraint was already ",
"set for this variable and both constraints set an upper bound.",
)
end
"""
## Transform Constraint Set
transform(model::ModelLike, c::ConstraintIndex{F,S1}, newset::S2)::ConstraintIndex{F,S2}
Replace the set in constraint `c` with `newset`. The constraint index `c`
will no longer be valid, and the function returns a new constraint index with
the correct type.
Solvers may only support a subset of constraint transforms that they perform
efficiently (for example, changing from a `LessThan` to `GreaterThan` set). In
addition, set modification (where `S1 = S2`) should be performed via the
`modify` function.
Typically, the user should delete the constraint and add a new one.
### Examples
If `c` is a `ConstraintIndex{ScalarAffineFunction{Float64},LessThan{Float64}}`,
```julia
c2 = transform(model, c, GreaterThan(0.0))
transform(model, c, LessThan(0.0)) # errors
```
"""
function transform end
# default fallback
function transform(model::ModelLike, c::ConstraintIndex, newset)
f = get(model, ConstraintFunction(), c)
delete(model, c)
return add_constraint(model, f, newset)
end