I formulate a mathematical optimization (MIQCP) problem with MathOptInterface.jl and solve it with Gurobi.jl. Certain variables in my problem are constrained (with MOI.add_constrained_variables(optimizer, [MOI.ZeroOne() for _ ∈ 1:n])) to be Boolean. After MOI.optimize!(optimizer) is done, I check that MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL, then retrieve the values of the variables using MOI.get(optimizer, MOI.VariablePrimal(), logical_vars).

To my surprise, the value of such a variable need not be integral, in one case it’s prevfloat(1.0, 7), very close to one, but not exactly integral.

Is this expected, or is it a bug?

I guess my workaround will be such, does it seem OK:

x = MOI.get(optimizer, MOI.VariablePrimal(), logical_variable)
ub = 1e-50
lb = true - 1e-10
((false ≤ x ≤ ub) | (lb ≤ x ≤ true)) || error("unexpected")
y = round(Bool, x)

Thanks! I ended up doing this, in addition to the above workaround:

import Gurobi as Opt
const gurobi_environment = Opt.Env()
import MathOptInterface as MOI
function make_opt()
opt = Opt.Optimizer(gurobi_environment)
MOI.set(opt, MOI.Silent(), true)
MOI.set(opt, MOI.RawOptimizerAttribute("IntegralityFocus"), true)
opt
end
my_program(make_opt, ...)

The MOI.Silent is there because otherwise Gurobi seems to print a line about setting the IntegralityFocus parameter each time that happens, which is each time an optimization problem gets formulated and solved.