Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve test coverage #153

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading