From 1e205915f8ff1ecf4436b4cd02519a3502163ea9 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 26 Nov 2024 20:02:30 +1300 Subject: [PATCH 1/2] Add support for DualObjectiveValue --- src/MOI_wrapper/MOI_wrapper.jl | 73 +++++++++++++++++++++++----------- test/MOI_wrapper.jl | 12 +++--- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 1fb8b2a..052ace5 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -400,44 +400,69 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) return Clp_getObjValue(model) end +function _active_bound(sense, d, l, u) + if -1e100 < l && u < 1e100 + return ifelse(sense * d >= 0, l, u) + elseif l > -1e100 + return l + elseif u < 1e100 + return u + else + return 0.0 + end +end + function MOI.get(model::Optimizer, attr::MOI.DualObjectiveValue) MOI.check_result_index_bounds(model, attr) - if MOI.get(model, MOI.DualStatus()) != MOI.INFEASIBILITY_CERTIFICATE - error("Unsupported") - end + has_dual_ray = + MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE M = Clp_getNumRows(model) - ray = _unsafe_wrap_clp_array(model, Clp_infeasibilityRay, M; own = true) + dual_objective_value = 0.0 + if !has_dual_ray + dual_objective_value -= Clp_objectiveOffset(model) + end + row_dual = if has_dual_ray + _unsafe_wrap_clp_array(model, Clp_infeasibilityRay, M; own = true) + else + _unsafe_wrap_clp_array(model, Clp_getRowPrice, M) + end l = _unsafe_wrap_clp_array(model, Clp_getRowLower, M) u = _unsafe_wrap_clp_array(model, Clp_getRowUpper, M) - obj = 0.0 + sense = Clp_getObjSense(model) for i in 1:M - # For scalar constraints the ray needs to be negated. - if -ray[i] < 0 - obj += ray[i] * u[i] - elseif -ray[i] > 0 - obj += ray[i] * l[i] + if has_dual_ray + b = _active_bound(sense, row_dual[i], l[i], u[i]) + dual_objective_value += -sense * b * row_dual[i] + else + dual_objective_value += + _active_bound(sense, row_dual[i], l[i], u[i]) * row_dual[i] end end N = Clp_getNumCols(model) l = _unsafe_wrap_clp_array(model, Clp_getColLower, N) u = _unsafe_wrap_clp_array(model, Clp_getColUpper, N) - nnz = Clp_getNumElements(model) - vbeg = _unsafe_wrap_clp_array(model, Clp_getVectorStarts, N) - vlen = _unsafe_wrap_clp_array(model, Clp_getVectorLengths, N) - vind = _unsafe_wrap_clp_array(model, Clp_getIndices, nnz) - vval = _unsafe_wrap_clp_array(model, Clp_getElements, nnz) - for col in 1:N - π = 0.0 - for i in vbeg[col] .+ (1:vlen[col]) - π += ray[vind[i]+1] * vval[i] + if has_dual_ray + nnz = Clp_getNumElements(model) + vbeg = _unsafe_wrap_clp_array(model, Clp_getVectorStarts, N) + vlen = _unsafe_wrap_clp_array(model, Clp_getVectorLengths, N) + vind = _unsafe_wrap_clp_array(model, Clp_getIndices, nnz) + vval = _unsafe_wrap_clp_array(model, Clp_getElements, nnz) + for col in 1:N + π = 0.0 + for i in vbeg[col] .+ (1:vlen[col]) + π[col] += row_dual[vind[i]+1] * vval[i] + end + dual_objective_value += + sense * _active_bound(sense, π[col], l[col], u[col]) * π[col] end - if π < 0 - obj += π * -u[col] - elseif π > 0 - obj += π * -l[col] + else + π = _unsafe_wrap_clp_array(model, Clp_getReducedCost, N) + for col in 1:N + dual_objective_value += + _active_bound(sense, π[col], l[col], u[col]) * π[col] end end - return -Clp_getObjSense(model) * obj + return dual_objective_value end function MOI.get(model::Optimizer, ::MOI.TerminationStatus) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index c2b6eae..9948740 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -6,10 +6,9 @@ module TestMOIWrapper using Test -using MathOptInterface -import Clp -const MOI = MathOptInterface +import Clp +import MathOptInterface as MOI function runtests() for name in names(@__MODULE__; all = true) @@ -19,6 +18,7 @@ function runtests() end end end + return end function test_SolverName() @@ -42,14 +42,12 @@ function test_runtests() # `Variable.ZerosBridge` makes dual needed by some tests fail. MOI.Bridges.remove_bridge( model.optimizer, - MathOptInterface.Bridges.Variable.ZerosBridge{Float64}, + MOI.Bridges.Variable.ZerosBridge{Float64}, ) MOI.set(model, MOI.Silent(), true) MOI.Test.runtests( model, - MOI.Test.Config( - exclude = Any[MOI.DualObjectiveValue, MOI.ObjectiveBound], - ), + MOI.Test.Config(; exclude = Any[MOI.ObjectiveBound]), exclude = [ # Unable to prove infeasibility "test_conic_NormInfinityCone_INFEASIBLE", From 355dca154f0d28dbfb761b34fc46ceccbc08532d Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 26 Nov 2024 20:15:23 +1300 Subject: [PATCH 2/2] Update --- src/MOI_wrapper/MOI_wrapper.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 052ace5..2a46fe1 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -450,10 +450,10 @@ function MOI.get(model::Optimizer, attr::MOI.DualObjectiveValue) for col in 1:N π = 0.0 for i in vbeg[col] .+ (1:vlen[col]) - π[col] += row_dual[vind[i]+1] * vval[i] + π += row_dual[vind[i]+1] * vval[i] end dual_objective_value += - sense * _active_bound(sense, π[col], l[col], u[col]) * π[col] + sense * _active_bound(sense, π, l[col], u[col]) * π end else π = _unsafe_wrap_clp_array(model, Clp_getReducedCost, N)