Hello everyone,
I stumbled on a potential bug in JuMP and its dependencies, however, I wanted to ask this question here first as I am unsure whether this intended behavior and (if not) on which library level this should be repaired.
For reference, I used the following library versions:
GLPK v1.1.3
Gurobi v1.0.2
JuMP v1.19.0
(all examples below assume using JuMP, GLPK, Gurobi
)
I observed that there are cases where calling value(x)
for a variable x
of an infeasible model will not throw an error but return some value.
For example, in the case of GLPK this behaviour can be triggered as follows:
model = Model(GLPK.Optimizer)
# Create variable x in [-1,1]
@variable(model, -1 <= x <= 1)
# Add constraint that x>=2 (Problem is thus infeasible)
@constraint(model, x >= 2)
optimize!(model)
@assert termination_status(model) == MOI.INFEASIBLE
# This should throw an error but instead returns an error
println(value(x))
Obviously, this mistake can be avoided by first checking has_values(model)
which correctly tells me that now model assignment exists, but to me it feels wrong that value
returns something other than an error – especially given value
is only supposed to provide primal values.
We can compare this to the behavior of Gurobi on the same problem which produces an error for the same problem:
model = Model(Gurobi.Optimizer)
@variable(model, -1 <= x <= 1)
@constraint(model, x >= 2)
optimize!(model)
@assert termination_status(model) == MOI.INFEASIBLE
# This throws an error:
println(value(x))
At first, I thought this was therefore only a problem with GLPK, but then I managed to track it to the different outcomes for MOI.get(model, MOI.ResultCount())
between the two models: GLPK returns 1
for this call as it has an infeasibility certificate.
Since MOI.check_result_index_bounds
uses MOI.ResultCount()
to check if there exists a result, this function does not throw an error and makes it so that value
returns something.
On the other hand, MOI.get(model, MOI.ResultCount())
returns 0
for Gurobi (this problem was solved by Gurobi’s presolve without an infeasibility certificate) and consequently MOI.check_result_index_bounds
throws an error.
As a consequence, when we deactivate Gurobi’s presolve (thus ensuring that Gurobi has an infeasibilty certificate) we also get a value from Gurobi:
model = Model(Gurobi.Optimizer)
# Deactivate Presolve -> ensures we get an infeasibility certificate
set_attribute(model, "Presolve", 0)
@variable(model, -1 <= x <= 1)
@constraint(model, x >= 2)
optimize!(model)
@assert termination_status(model) == MOI.INFEASIBLE
# This no longer throws an error:
println(value(x))
# Because we now have a positive result count:
@assert MOI.get(model, MOI.ResultCount()) >0
@assert model.moi_backend.optimizer.model.has_infeasibility_cert
This leaves me with the following questions:
Should value
be returning values for variables when the problem is infeasible?
If yes: What is the meaning of those values?
If no: On which level should this be fixed?
- MOI’s
check_result_index_bounds
currently callsget(model, ResultCount())
maybe there’s a better way to perform this check? - One could fix . the invocations of
check_result_index_bounds
in the individual solver libraries, but this seems like a lot of changes would be necessary and I am not sure which other libraries might have the same issue - One could introduce a check earlier up in the chain (e.g. checking for
has_values
upon an invocation ofvalue
So far I’ve only been a JuMP user, so I have not that much knowledge about the inner workings of JuMP. Thus, I thought it best to ask on here which option (if any) is the best way forward.
Thanks in advance,
Samuel