diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 2a46fe1..b0d1b1f 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -465,51 +465,33 @@ function MOI.get(model::Optimizer, attr::MOI.DualObjectiveValue) return dual_objective_value end +const _TerminationStatus = Dict{Int,Tuple{MOI.TerminationStatusCode,String}}( + 0 => (MOI.OPTIMAL, "0 - optimal"), + 1 => (MOI.INFEASIBLE, "1 - primal infeasible"), + 2 => (MOI.DUAL_INFEASIBLE, "2 - dual infeasible"), + # No more granular information that "some limit is reached" + 3 => (MOI.OTHER_LIMIT, "3 - stopped on iterations etc"), + 4 => (MOI.OTHER_ERROR, "4 - stopped due to errors"), +) + function MOI.get(model::Optimizer, ::MOI.TerminationStatus) if !model.optimize_called return MOI.OPTIMIZE_NOT_CALLED end - st = Clp_status(model) - if st == 0 - return MOI.OPTIMAL - elseif st == 1 - return MOI.INFEASIBLE - elseif st == 2 - return MOI.DUAL_INFEASIBLE - elseif st == 3 - # No more granular information that "some limit is reached" - return MOI.OTHER_LIMIT - end - return MOI.OTHER_ERROR + return _TerminationStatus[Clp_status(model)][1] end function MOI.get(model::Optimizer, ::MOI.RawStatusString) if !model.optimize_called return "MOI.OPTIMIZE_NOT_CALLED" end - st = Clp_status(model) - if st == 0 - return "0 - optimal" - elseif st == 1 - return "1 - primal infeasible" - elseif st == 2 - return "2 - dual infeasible" - elseif st == 3 - return "3 - stopped on iterations etc" - else - @assert st == 4 - return "4 - stopped due to errors" - end + return _TerminationStatus[Clp_status(model)][2] end function MOI.get(model::Optimizer, ::MOI.ResultCount) - if Clp_primalFeasible(model) != 0 - return 1 - elseif Clp_dualFeasible(model) != 0 + if Clp_primalFeasible(model) + Clp_isProvenPrimalInfeasible(model) > 0 return 1 - elseif Clp_isProvenPrimalInfeasible(model) != 0 - return 1 - elseif Clp_isProvenDualInfeasible(model) != 0 + elseif Clp_dualFeasible(model) + Clp_isProvenDualInfeasible(model) > 0 return 1 end return 0 @@ -549,9 +531,7 @@ function _unsafe_wrap_clp_array( own::Bool = false, ) p = f(model) - if p == C_NULL - return map(x -> NaN, indices) - end + @assert p != C_NULL x = unsafe_wrap(Array, p, (n,); own = own) return indices === nothing ? x : x[indices] end @@ -572,15 +552,13 @@ function MOI.get( x.value; own = true, ) - elseif primal_status == MOI.FEASIBLE_POINT - return _unsafe_wrap_clp_array( - model, - Clp_getColSolution, - Clp_getNumCols(model), - x.value, - ) end - return error("Primal solution not available") + return _unsafe_wrap_clp_array( + model, + Clp_getColSolution, + Clp_getNumCols(model), + x.value, + ) end function MOI.get( @@ -600,15 +578,13 @@ function MOI.get( col_indices; own = true, ) - elseif primal_status == MOI.FEASIBLE_POINT - return _unsafe_wrap_clp_array( - model, - Clp_getColSolution, - Clp_getNumCols(model), - col_indices, - ) end - return error("Primal solution not available") + return _unsafe_wrap_clp_array( + model, + Clp_getColSolution, + Clp_getNumCols(model), + col_indices, + ) end function MOI.get( @@ -667,7 +643,7 @@ function _farkas_variable_dual(model::Optimizer, col::Integer) vval = _unsafe_wrap_clp_array(model, Clp_getElements, nnz, indices) # We need to claim ownership of the pointer returned by Clp_infeasibilityRay. λ = _unsafe_wrap_clp_array(model, Clp_infeasibilityRay, m; own = true) - return sum(v * λ[i+1] for (i, v) in zip(vind, vval)) + return sum(v * λ[i+1] for (i, v) in zip(vind, vval); init = 0.0) end function MOI.get( @@ -679,10 +655,7 @@ function MOI.get( n = Clp_getNumRows(model) dual_status = MOI.get(model, MOI.DualStatus()) sense = Clp_getObjSense(model) - if dual_status == MOI.FEASIBLE_POINT - dsol = _unsafe_wrap_clp_array(model, Clp_getRowPrice, n, c.value) - return sense * dsol - elseif dual_status == MOI.INFEASIBILITY_CERTIFICATE + if dual_status == MOI.INFEASIBILITY_CERTIFICATE # We claim ownership of the pointer returned by Clp_infeasibilityRay. return -_unsafe_wrap_clp_array( model, @@ -692,7 +665,8 @@ function MOI.get( own = true, ) end - return error("Dual solution not available") + dsol = _unsafe_wrap_clp_array(model, Clp_getRowPrice, n, c.value) + return sense * dsol end function MOI.get( @@ -777,9 +751,7 @@ function _nonbasic_status(status, ::Type{<:MOI.GreaterThan}) return status == MOI.NONBASIC_AT_LOWER ? MOI.NONBASIC : MOI.BASIC end -_nonbasic_status(::Any, ::Type{<:MOI.EqualTo}) = MOI.NONBASIC - -_nonbasic_status(status, ::Type{<:MOI.Interval}) = status +_nonbasic_status(status, ::Type{S}) where {S} = status function MOI.get( model::Optimizer, diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 9948740..6fd7ba0 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -149,6 +149,96 @@ function test_attribute_TimeLimitSec() return end +function test_isProvenPrimalInfeasible() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + inner, + """ + variables: x, y + minobjective: -1.0 * x + -1.0 * y + -1.0 * x >= 1.0 + 1.0 * y >= 1.0 + x >= 0.0 + y >= 0.0 + """, + ) + model = Clp.Optimizer() + MOI.optimize!(model, inner) + @test MOI.get(model, MOI.RawStatusString()) == "1 - primal infeasible" + @test MOI.get(model, MOI.ResultCount()) == 1 + @test Clp.Clp_primalFeasible(model) == 0 + @test Clp.Clp_dualFeasible(model) == 0 + @test Clp.Clp_isProvenPrimalInfeasible(model) == 1 + return +end + +function test_isProvenDualInfeasible() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + inner, + """ + variables: x + maxobjective: 1.0 * x + x >= 0.0 + """, + ) + model = Clp.Optimizer() + MOI.optimize!(model, inner) + @test MOI.get(model, MOI.RawStatusString()) == "2 - dual infeasible" + @test MOI.get(model, MOI.ResultCount()) == 1 + @test Clp.Clp_primalFeasible(model) == 1 + @test Clp.Clp_dualFeasible(model) == 0 + @test Clp.Clp_isProvenDualInfeasible(model) == 1 + return +end + +function test_stopped_on_iterations() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + inner, + """ + variables: x1, x2, x3 + maxobjective: x1 + x2 + x1 + x2 + x3 <= 1.0 + x1 >= 0.0 + x2 >= 0.0 + x3 >= 0.0 + """, + ) + model = Clp.Optimizer() + @test MOI.get(model, MOI.RawStatusString()) == "MOI.OPTIMIZE_NOT_CALLED" + MOI.set(model, MOI.RawOptimizerAttribute("MaximumIterations"), 1) + MOI.set(model, MOI.RawOptimizerAttribute("PresolveType"), 1) + MOI.optimize!(model, inner) + @test MOI.get(model, MOI.RawStatusString()) == + "3 - stopped on iterations etc" + @test MOI.get(model, MOI.ResultCount()) == 0 + @test MOI.get(model, MOI.PrimalStatus()) == MOI.UNKNOWN_RESULT_STATUS + @test MOI.get(model, MOI.DualStatus()) == MOI.UNKNOWN_RESULT_STATUS + return +end + +function test_dual_infeasibility_certificate_fixed_bound() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + inner, + """ + variables: x, y + maxobjective: 1.0 * x + y + x >= 0.0 + 1.0 * x <= -1.0 + y == 1.0 + """, + ) + y = MOI.get(inner, MOI.VariableIndex, "y") + model = Clp.Optimizer() + index_map, _ = MOI.optimize!(model, inner) + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{Float64}}(y.value) + @test MOI.get(model, MOI.ConstraintDual(), index_map[ci]) == 0.0 + return +end + end # module TestMOIWrapper TestMOIWrapper.runtests()