Skip to content

[Bridges] improve error thrown in ToMILPBridge when variable is not bounded #2764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -512,4 +512,69 @@
return
end

"""
BridgeRequiresFiniteDomainError{
B<:AbstractBridge,
F<:MOI.AbstractFunction,
} <: Exception

An error thrown when the bridge requires the input function to have a finite
variable domain.
"""
struct BridgeRequiresFiniteDomainError{
B<:AbstractBridge,
F<:MOI.AbstractFunction,
} <: Exception
bridge::B
f::F
end

function Base.showerror(io::IO, err::BridgeRequiresFiniteDomainError)
return print(

Check warning on line 533 in src/Bridges/Bridges.jl

View check run for this annotation

Codecov / codecov/patch

src/Bridges/Bridges.jl#L532-L533

Added lines #L532 - L533 were not covered by tests
io,
"""
$(typeof(err)):

There was an error reformulating your model into a form supported by the
solver because one of the bridges requires that all variables have a
finite domain.

To fix this error, add a lower and upper bound to all variables in your
model.

If you have double checked that all variables have finite bounds and you
are still encountering this issue, please open a GitHub issue at
https://github.com/jump-dev/MathOptInterface.jl

## Common mistakes

A common mistake is to add the variable bounds as affine constraints. For
example, if you are using JuMP, do not use `@constraint` to add variable
bounds:
```julia
using JuMP
model = Model()
@variable(model, x)
@constraint(model, x >= 0)
@constraint(model, x <= 1)
```
do instead:
```julia
using JuMP
model = Model()
@variable(model, 0 <= x <= 1)
```

## Large bound values

Do not add arbitrarily large variable bounds to fix this error. Doing so
will likely result in a reformulation that takes a long time to build
and solve. Use domain knowledge to find the tightest valid bounds.

Alternatively, use a solver that has native support for the constraint
types you are using so that you do not need to use the bridging system.
""",
)
end

end # module Bridges
5 changes: 1 addition & 4 deletions src/Bridges/Constraint/bridges/BinPackingToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,7 @@ function MOI.Bridges.final_touch(
x = scalars[i]
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use $(typeof(bridge)) because an element in the " *
"function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i
# This is the first time calling final_touch
Expand Down
6 changes: 1 addition & 5 deletions src/Bridges/Constraint/bridges/CountBelongsToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,7 @@ function _unit_expansion(
for i in 1:length(f)
ret = MOI.Utilities.get_bounds(model, bounds, f[i])
if ret === nothing
BT = typeof(bridge)
error(
"Unable to use $BT because an element in the function has a " *
"non-finite domain: $(f[i])",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, f[i]))
end
if length(bridge.bounds) < i
# This is the first time calling final_touch
Expand Down
10 changes: 2 additions & 8 deletions src/Bridges/Constraint/bridges/CountDistinctToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,7 @@ function _final_touch_not_equal_case(
x = scalars[i]
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use CountDistinctToMILPBridge because element $i " *
"in the function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i - 1
# This is the first time calling final_touch
Expand Down Expand Up @@ -353,10 +350,7 @@ function _final_touch_general_case(
x = scalars[i]
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use CountDistinctToMILPBridge because element $i " *
"in the function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i - 1
# This is the first time calling final_touch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,7 @@ function _add_unit_expansion(
) where {T,F}
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use $(typeof(bridge)) because an element in the " *
"function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i
# This is the first time calling final_touch
Expand Down
5 changes: 1 addition & 4 deletions src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,7 @@ function MOI.Bridges.final_touch(
fi = scalars[2]
ret = MOI.Utilities.get_bounds(model, bounds, fi)
if ret === nothing
error(
"Unable to use IndicatorToMILPBridge because element 2 " *
"in the function has a non-finite domain: $fi",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, fi))
end
if bridge.slack === nothing
# This is the first time calling final_touch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,7 @@ function MOI.Bridges.final_touch(
x = scalars[i]
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use ReifiedCountDistinctToMILPBridge because " *
"element $i in the function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i - 2
# This is the first time calling final_touch
Expand Down
5 changes: 1 addition & 4 deletions src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,7 @@ function MOI.Bridges.final_touch(
for (i, x) in enumerate(scalars)
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use SOS1ToMILPBridge because element $i " *
"in the function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i
# This is the first time calling final_touch
Expand Down
5 changes: 1 addition & 4 deletions src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,7 @@ function MOI.Bridges.final_touch(
for (i, x) in enumerate(scalars)
ret = MOI.Utilities.get_bounds(model, bounds, x)
if ret === nothing
error(
"Unable to use SOS2ToMILPBridge because element $i " *
"in the function has a non-finite domain: $x",
)
throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, x))
end
if length(bridge.bounds) < i
# This is the first time calling final_touch
Expand Down
23 changes: 7 additions & 16 deletions test/Bridges/Constraint/BinPackingToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,10 @@ function test_runtests_error_variable()
model = MOI.Bridges.Constraint.BinPackingToMILP{Int}(inner)
x = MOI.add_variables(model, 3)
f = MOI.VectorOfVariables(x)
MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
BT =
MOI.Bridges.Constraint.BinPackingToMILPBridge{Int,MOI.VectorOfVariables}
c = MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
BT = typeof(model.map[c])
@test_throws(
ErrorException(
"Unable to use $BT because an element in the " *
"function has a non-finite domain: $(x[1])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
MOI.Bridges.final_touch(model),
)
return
Expand All @@ -136,16 +132,11 @@ function test_runtests_error_affine()
model = MOI.Bridges.Constraint.BinPackingToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
BT = MOI.Bridges.Constraint.BinPackingToMILPBridge{
Int,
MOI.VectorAffineFunction{Int},
}
c = MOI.add_constraint(model, f, MOI.BinPacking(3, [1, 2, 3]))
BT = typeof(model.map[c])
F = MOI.ScalarAffineFunction{Int}
@test_throws(
ErrorException(
"Unable to use $BT because an element in the " *
"function has a non-finite domain: $(1 * x[1])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
MOI.Bridges.final_touch(model),
)
return
Expand Down
25 changes: 7 additions & 18 deletions test/Bridges/Constraint/CountBelongsToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,10 @@ function test_runtests_error_variable()
model = MOI.Bridges.Constraint.CountBelongsToMILP{Int}(inner)
x = MOI.add_variables(model, 3)
f = MOI.VectorOfVariables(x)
MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
BT = MOI.Bridges.Constraint.CountBelongsToMILPBridge{
Int,
MOI.VectorOfVariables,
}
c = MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
BT = typeof(model.map[c])
@test_throws(
ErrorException(
"Unable to use $BT because an element in " *
"the function has a non-finite domain: $(x[2])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
MOI.Bridges.final_touch(model),
)
return
Expand All @@ -123,16 +117,11 @@ function test_runtests_error_affine()
model = MOI.Bridges.Constraint.CountBelongsToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
BT = MOI.Bridges.Constraint.CountBelongsToMILPBridge{
Int,
MOI.VectorAffineFunction{Int},
}
c = MOI.add_constraint(model, f, MOI.CountBelongs(3, Set([2, 4])))
BT = typeof(model.map[c])
F = MOI.ScalarAffineFunction{Int}
@test_throws(
ErrorException(
"Unable to use $BT because an element in " *
"the function has a non-finite domain: $(1 * x[1])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
MOI.Bridges.final_touch(model),
)
return
Expand Down
18 changes: 8 additions & 10 deletions test/Bridges/Constraint/CountDistinctToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,11 @@ function test_runtests_error_variable()
inner = MOI.Utilities.Model{Int}()
model = MOI.Bridges.Constraint.CountDistinctToMILP{Int}(inner)
x = MOI.add_variables(model, 3)
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.CountDistinct(3))
f = MOI.VectorOfVariables(x)
c = MOI.add_constraint(model, f, MOI.CountDistinct(3))
BT = typeof(model.map[c])
@test_throws(
ErrorException(
"Unable to use CountDistinctToMILPBridge because element 2 in " *
"the function has a non-finite domain: $(x[2])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
MOI.Bridges.final_touch(model),
)
return
Expand All @@ -160,12 +159,11 @@ function test_runtests_error_affine()
model = MOI.Bridges.Constraint.CountDistinctToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
f = MOI.Utilities.operate(vcat, Int, 2, 1 * x[1], x[2])
MOI.add_constraint(model, f, MOI.CountDistinct(3))
c = MOI.add_constraint(model, f, MOI.CountDistinct(3))
BT = typeof(model.map[c])
F = MOI.ScalarAffineFunction{Int}
@test_throws(
ErrorException(
"Unable to use CountDistinctToMILPBridge because element 2 in " *
"the function has a non-finite domain: $(1 * x[1])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
MOI.Bridges.final_touch(model),
)
return
Expand Down
25 changes: 7 additions & 18 deletions test/Bridges/Constraint/CountGreaterThanToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,10 @@ function test_runtests_error_variable()
model = MOI.Bridges.Constraint.CountGreaterThanToMILP{Int}(inner)
x = MOI.add_variables(model, 3)
f = MOI.VectorOfVariables(x)
MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
BT = MOI.Bridges.Constraint.CountGreaterThanToMILPBridge{
Int,
MOI.VectorOfVariables,
}
c = MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
BT = typeof(model.map[c])
@test_throws(
ErrorException(
"Unable to use $BT because an element in " *
"the function has a non-finite domain: $(x[2])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
MOI.Bridges.final_touch(model),
)
return
Expand All @@ -140,16 +134,11 @@ function test_runtests_error_affine()
model = MOI.Bridges.Constraint.CountGreaterThanToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
f = MOI.Utilities.operate(vcat, Int, 2, x[1], 1 * x[1], x[2])
MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
BT = MOI.Bridges.Constraint.CountGreaterThanToMILPBridge{
Int,
MOI.VectorAffineFunction{Int},
}
c = MOI.add_constraint(model, f, MOI.CountGreaterThan(3))
BT = typeof(model.map[c])
F = MOI.ScalarAffineFunction{Int}
@test_throws(
ErrorException(
"Unable to use $BT because an element in " *
"the function has a non-finite domain: $(1 * x[1])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
MOI.Bridges.final_touch(model),
)
return
Expand Down
17 changes: 7 additions & 10 deletions test/Bridges/Constraint/IndicatorToMILPBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,14 @@ function test_runtests_error_variable()
model = MOI.Bridges.Constraint.IndicatorToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
MOI.add_constraint(model, x[1], MOI.ZeroOne())
MOI.add_constraint(
c = MOI.add_constraint(
model,
MOI.VectorOfVariables(x),
MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)),
)
BT = typeof(model.map[c])
@test_throws(
ErrorException(
"Unable to use IndicatorToMILPBridge because element 2 in " *
"the function has a non-finite domain: $(x[2])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,MOI.VariableIndex},
MOI.Bridges.final_touch(model),
)
return
Expand All @@ -273,16 +271,15 @@ function test_runtests_error_affine()
model = MOI.Bridges.Constraint.IndicatorToMILP{Int}(inner)
x = MOI.add_variables(model, 2)
MOI.add_constraint(model, x[1], MOI.ZeroOne())
MOI.add_constraint(
c = MOI.add_constraint(
model,
MOI.Utilities.operate(vcat, Int, x[1], 2 * x[2]),
MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)),
)
BT = typeof(model.map[c])
F = MOI.ScalarAffineFunction{Int}
@test_throws(
ErrorException(
"Unable to use IndicatorToMILPBridge because element 2 in " *
"the function has a non-finite domain: $(2 * x[2])",
),
MOI.Bridges.BridgeRequiresFiniteDomainError{BT,F},
MOI.Bridges.final_touch(model),
)
return
Expand Down
Loading
Loading