Skip to content

Commit

Permalink
Improve test coverage (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Nov 27, 2024
1 parent 172e883 commit 231e39f
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 59 deletions.
90 changes: 31 additions & 59 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
90 changes: 90 additions & 0 deletions test/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit 231e39f

Please sign in to comment.