chriscoey / MOIPajarito.jl

temporary development repo for Pajarito with MathOptInterface
Mozilla Public License 2.0
7 stars 1 forks source link

DNM: trigger Gurobi get obj bound issue #22

Closed chriscoey closed 2 years ago

chriscoey commented 2 years ago

@odow

the issue is reproduced if you run include("test/myruntests.jl") (not the usual "runtests.jl")

what I see is:

no conic constraints need outer approximation
Set parameter FeasibilityTol to value 1e-09
Set parameter MIPGap to value 1e-09
Set parameter IntFeasTol to value 1e-09
Set parameter TimeLimit to value 9.9999999973702431e+05
JuMP.primal_status(opt.oa_model) = MathOptInterface.FEASIBLE_POINT
_soc1: Error During Test at /Users/coey/.julia/dev/MOIPajarito/test/JuMP_tests.jl:34
  Got exception outside of a @test
  Gurobi Error 10005: Unable to retrieve attribute 'ObjBound'
  Stacktrace:
    [1] _check_ret
      @ ~/.julia/packages/Gurobi/A9YqC/src/MOI_wrapper/MOI_wrapper.jl:325 [inlined]
    [2] get(model::Gurobi.Optimizer, attr::MathOptInterface.ObjectiveBound)
      @ Gurobi ~/.julia/packages/Gurobi/A9YqC/src/MOI_wrapper/MOI_wrapper.jl:3141
    [3] get (repeats 2 times)
      @ ~/.julia/packages/MathOptInterface/eoIu0/src/Bridges/bridge_optimizer.jl:815 [inlined]
    [4] get(model::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{Gurobi.Optimizer}}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}, attr::MathOptInterface.ObjectiveBound)
      @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/eoIu0/src/Utilities/cachingoptimizer.jl:803
    [5] _moi_get_result(model::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{Gurobi.Optimizer}}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}, args::MathOptInterface.ObjectiveBound)
      @ JuMP ~/.julia/packages/JuMP/2IF9U/src/JuMP.jl:1226
    [6] get(model::JuMP.Model, attr::MathOptInterface.ObjectiveBound)
      @ JuMP ~/.julia/packages/JuMP/2IF9U/src/JuMP.jl:1251
    [7] objective_bound(model::JuMP.Model)
      @ JuMP ~/.julia/packages/JuMP/2IF9U/src/objective.jl:30
    [8] setup_models(opt::MOIPajarito.Optimizer)
      @ MOIPajarito ~/.julia/dev/MOIPajarito/src/optimize.jl:499
    [9] optimize(opt::MOIPajarito.Optimizer)
      @ MOIPajarito ~/.julia/dev/MOIPajarito/src/optimize.jl:95
   [10] optimize!
      @ ~/.julia/dev/MOIPajarito/src/MOI_wrapper.jl:212 [inlined]
   [11] optimize!
      @ ~/.julia/packages/MathOptInterface/eoIu0/src/MathOptInterface.jl:81 [inlined]
   [12] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{MOIPajarito.Optimizer, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}})
      @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/eoIu0/src/Utilities/cachingoptimizer.jl:285
   [13] optimize!
      @ ~/.julia/packages/MathOptInterface/eoIu0/src/Bridges/bridge_optimizer.jl:348 [inlined]
   [14] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{MOIPajarito.Optimizer, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}}}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}})
      @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/eoIu0/src/Utilities/cachingoptimizer.jl:294
   [15] optimize!(model::JuMP.Model, optimizer_factory::Nothing; bridge_constraints::Bool, ignore_optimize_hook::Bool, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
      @ JuMP ~/.julia/packages/JuMP/2IF9U/src/optimizer_interface.jl:195
   [16] optimize! (repeats 2 times)
      @ ~/.julia/packages/JuMP/2IF9U/src/optimizer_interface.jl:167 [inlined]
   [17] _soc1(opt::MathOptInterface.OptimizerWithAttributes)
      @ Main.TestJuMP ~/.julia/dev/MOIPajarito/test/JuMP_tests.jl:53

here is the line where it occurs: https://github.com/chriscoey/MOIPajarito.jl/blob/e161bc8f0a1559f970b66eb8b3b2ca141277f063/src/optimize.jl#L498

it is solving a MISOCP with Gurobi just a few lines above that (MOIPajarito does not use outer approximation because Gurobi already supports MISOCP and this instance is MISOCP).

odow commented 2 years ago

Do you have the Gurobi log? I don't have a good solution for you. That's an internal Gurobi error, not from us.

What happens if you use Gurobi directly? There might be other parameters that you need to set.

chriscoey commented 2 years ago

OK I think I figured it out. the problem being solved is continuous, not MISOCP. so we can't call objective_bound:

using Test
import JuMP
import Gurobi
const MOI = JuMP.MOI
TOL = 1e-4

m = JuMP.Model(Gurobi.Optimizer)
JuMP.@variable(m, x)
JuMP.@objective(m, Min, -x)
JuMP.@constraint(m, [3.5, x] in JuMP.SecondOrderCone())
JuMP.optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
@test isapprox(JuMP.objective_value(m), -3.5, atol = TOL)
@test isapprox(JuMP.value(x), 3.5, atol = TOL)
@test isapprox(JuMP.dual_objective_value(m), -3.5, atol = TOL) # EDIT: also ERROR
@test isapprox(JuMP.objective_bound(m), -3.5, atol = TOL)   # ERROR

So I can fix this in MOIPajarito.

In general I don't know whether it is intended that objective_bound only work for MIPs and not for continuous problems. But I guess it was Gurobi's choice in this case.

chriscoey commented 2 years ago

actually, JuMP.dual_objective_value(m) errors too (same error). so I can't get dual obj or obj bound for this SOCP. that's a bit surprising

odow commented 2 years ago

Yeah that's a deliberate Gurobi choice. The perils of solver-independence...

You should probably use a try-catch.

chriscoey commented 2 years ago

eh i'll just use the primal objective value

chriscoey commented 2 years ago

changed my mind. does this look OK? I'm not 100% sure if there's a problem with using return inside try.

# try to get an objective bound or dual objective value, else use the primal objective value
function get_objective_bound(model::JuMP.Model)
    try
        return JuMP.objective_bound(model)
    catch
    end
    try
        return JuMP.dual_objective_value(model)
    catch
    end
    return JuMP.objective_value(model)
end
odow commented 2 years ago

Using return inside try is fine.

Is this for MILP models? What even is the dual objective value?

chriscoey commented 2 years ago

the OA solver isn't necessarily a MIP solver. at some point with a separation-cuts-only algorithm, we can give cuts to a conic solver that doesn't support all the cones we want