Skip to content

Commit a53fedb

Browse files
authored
[Bridges] improve error thrown in ToMILPBridge when variable is not bounded (#2764)
1 parent bc869ec commit a53fedb

18 files changed

+142
-139
lines changed

src/Bridges/Bridges.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,4 +512,69 @@ function _general_bridge_tests(bridge::B) where {B<:AbstractBridge}
512512
return
513513
end
514514

515+
"""
516+
BridgeRequiresFiniteDomainError{
517+
B<:AbstractBridge,
518+
F<:MOI.AbstractFunction,
519+
} <: Exception
520+
521+
An error thrown when the bridge requires the input function to have a finite
522+
variable domain.
523+
"""
524+
struct BridgeRequiresFiniteDomainError{
525+
B<:AbstractBridge,
526+
F<:MOI.AbstractFunction,
527+
} <: Exception
528+
bridge::B
529+
f::F
515530
end
531+
532+
function Base.showerror(io::IO, err::BridgeRequiresFiniteDomainError)
533+
return print(
534+
io,
535+
"""
536+
$(typeof(err)):
537+
538+
There was an error reformulating your model into a form supported by the
539+
solver because one of the bridges requires that all variables have a
540+
finite domain.
541+
542+
To fix this error, add a lower and upper bound to all variables in your
543+
model.
544+
545+
If you have double checked that all variables have finite bounds and you
546+
are still encountering this issue, please open a GitHub issue at
547+
https://github.com/jump-dev/MathOptInterface.jl
548+
549+
## Common mistakes
550+
551+
A common mistake is to add the variable bounds as affine constraints. For
552+
example, if you are using JuMP, do not use `@constraint` to add variable
553+
bounds:
554+
```julia
555+
using JuMP
556+
model = Model()
557+
@variable(model, x)
558+
@constraint(model, x >= 0)
559+
@constraint(model, x <= 1)
560+
```
561+
do instead:
562+
```julia
563+
using JuMP
564+
model = Model()
565+
@variable(model, 0 <= x <= 1)
566+
```
567+
568+
## Large bound values
569+
570+
Do not add arbitrarily large variable bounds to fix this error. Doing so
571+
will likely result in a reformulation that takes a long time to build
572+
and solve. Use domain knowledge to find the tightest valid bounds.
573+
574+
Alternatively, use a solver that has native support for the constraint
575+
types you are using so that you do not need to use the bridging system.
576+
""",
577+
)
578+
end
579+
580+
end # module Bridges

src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,7 @@ function MOI.Bridges.final_touch(
205205
x = scalars[i]
206206
ret = MOI.Utilities.get_bounds(model, bounds, x)
207207
if ret === nothing
208-
error(
209-
"Unable to use $(typeof(bridge)) because an element in the " *
210-
"function has a non-finite domain: $x",
211-
)
208+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
212209
end
213210
if length(bridge.bounds) < i
214211
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,7 @@ function _unit_expansion(
210210
for i in 1:length(f)
211211
ret = MOI.Utilities.get_bounds(model, bounds, f[i])
212212
if ret === nothing
213-
BT = typeof(bridge)
214-
error(
215-
"Unable to use $BT because an element in the function has a " *
216-
"non-finite domain: $(f[i])",
217-
)
213+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, f[i]))
218214
end
219215
if length(bridge.bounds) < i
220216
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,7 @@ function _final_touch_not_equal_case(
277277
x = scalars[i]
278278
ret = MOI.Utilities.get_bounds(model, bounds, x)
279279
if ret === nothing
280-
error(
281-
"Unable to use CountDistinctToMILPBridge because element $i " *
282-
"in the function has a non-finite domain: $x",
283-
)
280+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
284281
end
285282
if length(bridge.bounds) < i - 1
286283
# This is the first time calling final_touch
@@ -353,10 +350,7 @@ function _final_touch_general_case(
353350
x = scalars[i]
354351
ret = MOI.Utilities.get_bounds(model, bounds, x)
355352
if ret === nothing
356-
error(
357-
"Unable to use CountDistinctToMILPBridge because element $i " *
358-
"in the function has a non-finite domain: $x",
359-
)
353+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
360354
end
361355
if length(bridge.bounds) < i - 1
362356
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/CountGreaterThanToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,7 @@ function _add_unit_expansion(
192192
) where {T,F}
193193
ret = MOI.Utilities.get_bounds(model, bounds, x)
194194
if ret === nothing
195-
error(
196-
"Unable to use $(typeof(bridge)) because an element in the " *
197-
"function has a non-finite domain: $x",
198-
)
195+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
199196
end
200197
if length(bridge.bounds) < i
201198
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,7 @@ function MOI.Bridges.final_touch(
197197
fi = scalars[2]
198198
ret = MOI.Utilities.get_bounds(model, bounds, fi)
199199
if ret === nothing
200-
error(
201-
"Unable to use IndicatorToMILPBridge because element 2 " *
202-
"in the function has a non-finite domain: $fi",
203-
)
200+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, fi))
204201
end
205202
if bridge.slack === nothing
206203
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/ReifiedCountDistinctToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,7 @@ function MOI.Bridges.final_touch(
257257
x = scalars[i]
258258
ret = MOI.Utilities.get_bounds(model, bounds, x)
259259
if ret === nothing
260-
error(
261-
"Unable to use ReifiedCountDistinctToMILPBridge because " *
262-
"element $i in the function has a non-finite domain: $x",
263-
)
260+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
264261
end
265262
if length(bridge.bounds) < i - 2
266263
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,7 @@ function MOI.Bridges.final_touch(
203203
for (i, x) in enumerate(scalars)
204204
ret = MOI.Utilities.get_bounds(model, bounds, x)
205205
if ret === nothing
206-
error(
207-
"Unable to use SOS1ToMILPBridge because element $i " *
208-
"in the function has a non-finite domain: $x",
209-
)
206+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
210207
end
211208
if length(bridge.bounds) < i
212209
# This is the first time calling final_touch

src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,7 @@ function MOI.Bridges.final_touch(
203203
for (i, x) in enumerate(scalars)
204204
ret = MOI.Utilities.get_bounds(model, bounds, x)
205205
if ret === nothing
206-
error(
207-
"Unable to use SOS2ToMILPBridge because element $i " *
208-
"in the function has a non-finite domain: $x",
209-
)
206+
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
210207
end
211208
if length(bridge.bounds) < i
212209
# This is the first time calling final_touch

test/Bridges/Constraint/BinPackingToMILPBridge.jl

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,10 @@ function test_runtests_error_variable()
118118
model = MOI.Bridges.Constraint.BinPackingToMILP{Int}(inner)
119119
x = MOI.add_variables(model, 3)
120120
f = MOI.VectorOfVariables(x)
121-
MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
122-
BT =
123-
MOI.Bridges.Constraint.BinPackingToMILPBridge{Int,MOI.VectorOfVariables}
121+
c = MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
122+
BT = typeof(model.map[c])
124123
@test_throws(
125-
ErrorException(
126-
"Unable to use $BT because an element in the " *
127-
"function has a non-finite domain: $(x[1])",
128-
),
124+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
129125
MOI.Bridges.final_touch(model),
130126
)
131127
return
@@ -136,16 +132,11 @@ function test_runtests_error_affine()
136132
model = MOI.Bridges.Constraint.BinPackingToMILP{Int}(inner)
137133
x = MOI.add_variables(model, 2)
138134
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
139-
MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
140-
BT = MOI.Bridges.Constraint.BinPackingToMILPBridge{
141-
Int,
142-
MOI.VectorAffineFunction{Int},
143-
}
135+
c = MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
136+
BT = typeof(model.map[c])
137+
F = MOI.ScalarAffineFunction{Int}
144138
@test_throws(
145-
ErrorException(
146-
"Unable to use $BT because an element in the " *
147-
"function has a non-finite domain: $(1 * x[1])",
148-
),
139+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
149140
MOI.Bridges.final_touch(model),
150141
)
151142
return

test/Bridges/Constraint/CountBelongsToMILPBridge.jl

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,10 @@ function test_runtests_error_variable()
103103
model = MOI.Bridges.Constraint.CountBelongsToMILP{Int}(inner)
104104
x = MOI.add_variables(model, 3)
105105
f = MOI.VectorOfVariables(x)
106-
MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
107-
BT = MOI.Bridges.Constraint.CountBelongsToMILPBridge{
108-
Int,
109-
MOI.VectorOfVariables,
110-
}
106+
c = MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
107+
BT = typeof(model.map[c])
111108
@test_throws(
112-
ErrorException(
113-
"Unable to use $BT because an element in " *
114-
"the function has a non-finite domain: $(x[2])",
115-
),
109+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
116110
MOI.Bridges.final_touch(model),
117111
)
118112
return
@@ -123,16 +117,11 @@ function test_runtests_error_affine()
123117
model = MOI.Bridges.Constraint.CountBelongsToMILP{Int}(inner)
124118
x = MOI.add_variables(model, 2)
125119
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
126-
MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
127-
BT = MOI.Bridges.Constraint.CountBelongsToMILPBridge{
128-
Int,
129-
MOI.VectorAffineFunction{Int},
130-
}
120+
c = MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
121+
BT = typeof(model.map[c])
122+
F = MOI.ScalarAffineFunction{Int}
131123
@test_throws(
132-
ErrorException(
133-
"Unable to use $BT because an element in " *
134-
"the function has a non-finite domain: $(1 * x[1])",
135-
),
124+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
136125
MOI.Bridges.final_touch(model),
137126
)
138127
return

test/Bridges/Constraint/CountDistinctToMILPBridge.jl

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,11 @@ function test_runtests_error_variable()
144144
inner = MOI.Utilities.Model{Int}()
145145
model = MOI.Bridges.Constraint.CountDistinctToMILP{Int}(inner)
146146
x = MOI.add_variables(model, 3)
147-
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.CountDistinct(3))
147+
f = MOI.VectorOfVariables(x)
148+
c = MOI.add_constraint(model, f, MOI.CountDistinct(3))
149+
BT = typeof(model.map[c])
148150
@test_throws(
149-
ErrorException(
150-
"Unable to use CountDistinctToMILPBridge because element 2 in " *
151-
"the function has a non-finite domain: $(x[2])",
152-
),
151+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
153152
MOI.Bridges.final_touch(model),
154153
)
155154
return
@@ -160,12 +159,11 @@ function test_runtests_error_affine()
160159
model = MOI.Bridges.Constraint.CountDistinctToMILP{Int}(inner)
161160
x = MOI.add_variables(model, 2)
162161
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
163-
MOI.add_constraint(model, f, MOI.CountDistinct(3))
162+
c = MOI.add_constraint(model, f, MOI.CountDistinct(3))
163+
BT = typeof(model.map[c])
164+
F = MOI.ScalarAffineFunction{Int}
164165
@test_throws(
165-
ErrorException(
166-
"Unable to use CountDistinctToMILPBridge because element 2 in " *
167-
"the function has a non-finite domain: $(1 * x[1])",
168-
),
166+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
169167
MOI.Bridges.final_touch(model),
170168
)
171169
return

test/Bridges/Constraint/CountGreaterThanToMILPBridge.jl

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,10 @@ function test_runtests_error_variable()
120120
model = MOI.Bridges.Constraint.CountGreaterThanToMILP{Int}(inner)
121121
x = MOI.add_variables(model, 3)
122122
f = MOI.VectorOfVariables(x)
123-
MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
124-
BT = MOI.Bridges.Constraint.CountGreaterThanToMILPBridge{
125-
Int,
126-
MOI.VectorOfVariables,
127-
}
123+
c = MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
124+
BT = typeof(model.map[c])
128125
@test_throws(
129-
ErrorException(
130-
"Unable to use $BT because an element in " *
131-
"the function has a non-finite domain: $(x[2])",
132-
),
126+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
133127
MOI.Bridges.final_touch(model),
134128
)
135129
return
@@ -140,16 +134,11 @@ function test_runtests_error_affine()
140134
model = MOI.Bridges.Constraint.CountGreaterThanToMILP{Int}(inner)
141135
x = MOI.add_variables(model, 2)
142136
f = MOI.Utilities.operate(vcat, Int, 2, x[1], 1 * x[1], x[2])
143-
MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
144-
BT = MOI.Bridges.Constraint.CountGreaterThanToMILPBridge{
145-
Int,
146-
MOI.VectorAffineFunction{Int},
147-
}
137+
c = MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
138+
BT = typeof(model.map[c])
139+
F = MOI.ScalarAffineFunction{Int}
148140
@test_throws(
149-
ErrorException(
150-
"Unable to use $BT because an element in " *
151-
"the function has a non-finite domain: $(1 * x[1])",
152-
),
141+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
153142
MOI.Bridges.final_touch(model),
154143
)
155144
return

test/Bridges/Constraint/IndicatorToMILPBridge.jl

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,16 +253,14 @@ function test_runtests_error_variable()
253253
model = MOI.Bridges.Constraint.IndicatorToMILP{Int}(inner)
254254
x = MOI.add_variables(model, 2)
255255
MOI.add_constraint(model, x[1], MOI.ZeroOne())
256-
MOI.add_constraint(
256+
c = MOI.add_constraint(
257257
model,
258258
MOI.VectorOfVariables(x),
259259
MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)),
260260
)
261+
BT = typeof(model.map[c])
261262
@test_throws(
262-
ErrorException(
263-
"Unable to use IndicatorToMILPBridge because element 2 in " *
264-
"the function has a non-finite domain: $(x[2])",
265-
),
263+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
266264
MOI.Bridges.final_touch(model),
267265
)
268266
return
@@ -273,16 +271,15 @@ function test_runtests_error_affine()
273271
model = MOI.Bridges.Constraint.IndicatorToMILP{Int}(inner)
274272
x = MOI.add_variables(model, 2)
275273
MOI.add_constraint(model, x[1], MOI.ZeroOne())
276-
MOI.add_constraint(
274+
c = MOI.add_constraint(
277275
model,
278276
MOI.Utilities.operate(vcat, Int, x[1], 2 * x[2]),
279277
MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)),
280278
)
279+
BT = typeof(model.map[c])
280+
F = MOI.ScalarAffineFunction{Int}
281281
@test_throws(
282-
ErrorException(
283-
"Unable to use IndicatorToMILPBridge because element 2 in " *
284-
"the function has a non-finite domain: $(2 * x[2])",
285-
),
282+
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
286283
MOI.Bridges.final_touch(model),
287284
)
288285
return

0 commit comments

Comments
 (0)