From adf14391b15849144410728ad4ed2a69fce57243 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 23 Sep 2024 11:03:39 +1200 Subject: [PATCH 1/2] Fix ObjectiveBound and RelativeGap for LP models --- src/MOI_wrapper.jl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index b2fad53..58894f6 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -2103,14 +2103,12 @@ function _dual_objective_contribution(l, x, u, d) end function MOI.get(model::Optimizer, ::MOI.ObjectiveBound) - # TODO(odow): there is a bug in HiGHS where it reports incorrect values for - # mip_dual_bound in the LP case. As a work-around, just return the most - # optimistic of the primal and dual values. + if model.solution.dual_solution_status != kHighsSolutionStatusNone + return MOI.get(model, MOI.DualObjectiveValue()) + end p = Ref{Cdouble}() Highs_getDoubleInfoValue(model, "mip_dual_bound", p) - sense = _sense_corrector(model) - primal = Highs_getObjectiveValue(model) - return sense == -1 ? max(primal, -p[]) : min(primal, p[]) + return p[] end function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) @@ -2118,7 +2116,12 @@ function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) end function MOI.get(model::Optimizer, ::MOI.RelativeGap) - p = Ref{Cdouble}(0) + if model.solution.dual_solution_status != kHighsSolutionStatusNone + primal = MOI.get(model, MOI.ObjectiveValue()) + dual = MOI.get(model, MOI.DualObjectiveValue()) + return abs(primal - dual) / max(1, abs(primal)) + end + p = Ref{Cdouble}(0.0) ret = Highs_getDoubleInfoValue(model, "mip_gap", p) _check_ret(ret) return p[] From 3fdb5fdcce68733c3a1312663bb23b4b737d61c8 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 24 Sep 2024 09:50:26 +1200 Subject: [PATCH 2/2] Add test --- test/MOI_wrapper.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 73dbea4..23d93fe 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -930,6 +930,25 @@ function test_DualObjectiveValue_int() return end +function test_continuous_objective_bound() + model = HiGHS.Optimizer() + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("solver"), "ipm") + MOI.set(model, MOI.RawOptimizerAttribute("run_crossover"), "off") + MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off") + x = MOI.add_variables(model, 3) + c = MOI.add_constraint.(model, 1.0 .* x, MOI.EqualTo.(1.0:3.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x[1] + x[2] + x[3] + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + MOI.optimize!(model) + primal = MOI.get(model, MOI.ObjectiveValue()) + dual = MOI.get(model, MOI.DualObjectiveValue()) + @test MOI.get(model, MOI.ObjectiveBound()) == dual + @test 0 < MOI.get(model, MOI.RelativeGap()) <= 1e-6 + return +end + end # module TestMOIHighs.runtests()