Skip to content

Commit

Permalink
Add tests to improve code coverage (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Jun 18, 2024
1 parent e3b019d commit b5d5a3f
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 81 deletions.
118 changes: 39 additions & 79 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,6 @@ function _get_string_option(model::Optimizer, option::String)
end

function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute)
if !(param.name isa String)
throw(MOI.UnsupportedAttribute(param))
end
typeP = Ref{HighsInt}()
ret = Highs_getOptionType(model, param.name, typeP)
if ret != 0
Expand Down Expand Up @@ -1458,14 +1455,6 @@ function MOI.set(
return
end

function MOI.supports(
::Optimizer,
::MOI.ConstraintName,
::Type{MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}},
) where {S}
return true
end

###
### ScalarAffineFunction-in-Set
###
Expand Down Expand Up @@ -1940,47 +1929,50 @@ function MOI.optimize!(model::Optimizer)
return
end

const _TerminationStatusMap = Dict(
kHighsModelStatusNotset => (MOI.OTHER_ERROR, "kHighsModelStatusNotset"),
kHighsModelStatusLoadError =>
(MOI.OTHER_ERROR, "kHighsModelStatusLoadError"),
kHighsModelStatusModelError =>
(MOI.INVALID_MODEL, "kHighsModelStatusModelError"),
kHighsModelStatusPresolveError =>
(MOI.OTHER_ERROR, "kHighsModelStatusPresolveError"),
kHighsModelStatusSolveError =>
(MOI.OTHER_ERROR, "kHighsModelStatusSolveError"),
kHighsModelStatusPostsolveError =>
(MOI.OTHER_ERROR, "kHighsModelStatusPostsolveError"),
kHighsModelStatusModelEmpty =>
(MOI.INVALID_MODEL, "kHighsModelStatusModelEmpty"),
kHighsModelStatusOptimal => (MOI.OPTIMAL, "kHighsModelStatusOptimal"),
kHighsModelStatusInfeasible =>
(MOI.INFEASIBLE, "kHighsModelStatusInfeasible"),
kHighsModelStatusUnboundedOrInfeasible => (
MOI.INFEASIBLE_OR_UNBOUNDED,
"kHighsModelStatusUnboundedOrInfeasible",
),
kHighsModelStatusUnbounded =>
(MOI.DUAL_INFEASIBLE, "kHighsModelStatusUnbounded"),
kHighsModelStatusObjectiveBound =>
(MOI.OBJECTIVE_LIMIT, "kHighsModelStatusObjectiveBound"),
kHighsModelStatusObjectiveTarget =>
(MOI.OBJECTIVE_LIMIT, "kHighsModelStatusObjectiveTarget"),
kHighsModelStatusTimeLimit =>
(MOI.TIME_LIMIT, "kHighsModelStatusTimeLimit"),
kHighsModelStatusIterationLimit =>
(MOI.ITERATION_LIMIT, "kHighsModelStatusIterationLimit"),
kHighsModelStatusUnknown =>
(MOI.OTHER_ERROR, "kHighsModelStatusUnknown"),
kHighsModelStatusSolutionLimit =>
(MOI.SOLUTION_LIMIT, "kHighsModelStatusSolutionLimit"),
)

function MOI.get(model::Optimizer, ::MOI.TerminationStatus)
if model.solution.status == _OPTIMIZE_NOT_CALLED
return MOI.OPTIMIZE_NOT_CALLED
elseif model.solution.status == _OPTIMIZE_ERRORED
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusNotset
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusLoadError
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusModelError
return MOI.INVALID_MODEL
elseif model.solution.model_status == kHighsModelStatusPresolveError
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusSolveError
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusPostsolveError
return MOI.OTHER_ERROR
elseif model.solution.model_status == kHighsModelStatusModelEmpty
return MOI.INVALID_MODEL
elseif model.solution.model_status == kHighsModelStatusOptimal
return MOI.OPTIMAL
elseif model.solution.model_status == kHighsModelStatusInfeasible
return MOI.INFEASIBLE
elseif model.solution.model_status == kHighsModelStatusUnboundedOrInfeasible
return MOI.INFEASIBLE_OR_UNBOUNDED
elseif model.solution.model_status == kHighsModelStatusUnbounded
return MOI.DUAL_INFEASIBLE
elseif model.solution.model_status == kHighsModelStatusObjectiveBound
return MOI.OBJECTIVE_LIMIT
elseif model.solution.model_status == kHighsModelStatusObjectiveTarget
return MOI.OBJECTIVE_LIMIT
elseif model.solution.model_status == kHighsModelStatusTimeLimit
return MOI.TIME_LIMIT
elseif model.solution.model_status == kHighsModelStatusIterationLimit
return MOI.ITERATION_LIMIT
elseif model.solution.model_status == kHighsModelStatusUnknown
return MOI.OTHER_ERROR
else
@assert model.solution.model_status == kHighsModelStatusSolutionLimit
return MOI.SOLUTION_LIMIT
end
return _TerminationStatusMap[model.solution.model_status][1]
end

function MOI.get(model::Optimizer, ::MOI.ResultCount)
Expand All @@ -1999,40 +1991,8 @@ function MOI.get(model::Optimizer, ::MOI.RawStatusString)
return "OPTIMIZE_NOT_CALLED"
elseif model.solution.status == _OPTIMIZE_ERRORED
return "There was an error calling optimize!"
elseif model.solution.model_status == kHighsModelStatusNotset
return "kHighsModelStatusNotset"
elseif model.solution.model_status == kHighsModelStatusLoadError
return "kHighsModelStatusLoadError"
elseif model.solution.model_status == kHighsModelStatusModelError
return "kHighsModelStatusModelError"
elseif model.solution.model_status == kHighsModelStatusPresolveError
return "kHighsModelStatusPresolveError"
elseif model.solution.model_status == kHighsModelStatusSolveError
return "kHighsModelStatusSolveError"
elseif model.solution.model_status == kHighsModelStatusPostsolveError
return "kHighsModelStatusPostsolveError"
elseif model.solution.model_status == kHighsModelStatusModelEmpty
return "kHighsModelStatusModelEmpty"
elseif model.solution.model_status == kHighsModelStatusOptimal
return "kHighsModelStatusOptimal"
elseif model.solution.model_status == kHighsModelStatusInfeasible
return "kHighsModelStatusInfeasible"
elseif model.solution.model_status == kHighsModelStatusUnboundedOrInfeasible
return "kHighsModelStatusUnboundedOrInfeasible"
elseif model.solution.model_status == kHighsModelStatusUnbounded
return "kHighsModelStatusUnbounded"
elseif model.solution.model_status == kHighsModelStatusObjectiveBound
return "kHighsModelStatusObjectiveBound"
elseif model.solution.model_status == kHighsModelStatusObjectiveTarget
return "kHighsModelStatusObjectiveTarget"
elseif model.solution.model_status == kHighsModelStatusTimeLimit
return "kHighsModelStatusTimeLimit"
elseif model.solution.model_status == kHighsModelStatusIterationLimit
return "kHighsModelStatusIterationLimit"
else
@assert model.solution.model_status == kHighsModelStatusUnknown
return "kHighsModelStatusUnknown"
end
return _TerminationStatusMap[model.solution.model_status][2]
end

function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
Expand Down
111 changes: 109 additions & 2 deletions test/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

module TestMOIHighs

import HiGHS
using Test

const MOI = HiGHS.MOI
import HiGHS
import MathOptInterface as MOI

function runtests()
for name in names(@__MODULE__; all = true)
Expand Down Expand Up @@ -741,6 +741,113 @@ function test_write_mps_gz()
return
end

function test_get_empty_objective_function()
model = HiGHS.Optimizer()
F = MOI.ScalarAffineFunction{Float64}
f = MOI.get(model, MOI.ObjectiveFunction{F}())
@test f zero(F)
return
end

function test_is_valid_variable_bound()
model = HiGHS.Optimizer()
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}(1)
@test_throws MOI.InvalidIndex HiGHS.column(model, ci)
@test !MOI.is_valid(model, ci)
return
end

function test_set_function_variable()
model = HiGHS.Optimizer()
x, ci = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0))
y = MOI.add_variable(model)
@test_throws(
MOI.SettingVariableIndexNotAllowed,
MOI.set(model, MOI.ConstraintFunction(), ci, y),
)
return
end

function test_throw_if_existing_bound()
model = HiGHS.Optimizer()
x, ci = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0))
MOI.add_constraint(model, x, MOI.LessThan(1.0))
@test_throws(
MOI.LowerBoundAlreadySet,
MOI.add_constraint(model, x, MOI.GreaterThan(1.0)),
)
@test_throws(
MOI.UpperBoundAlreadySet,
MOI.add_constraint(model, x, MOI.LessThan(0.5)),
)
return
end

function test_delete_double_bound_less_than()
model = HiGHS.Optimizer()
x = MOI.add_variable(model)
_ = MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
ci = MOI.add_constraint(model, x, MOI.LessThan(1.0))
@test MOI.is_valid(model, ci)
MOI.delete(model, ci)
@test !MOI.is_valid(model, ci)
return
end

function test_delete_double_bound_greater_than()
model = HiGHS.Optimizer()
x = MOI.add_variable(model)
ci = MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
_ = MOI.add_constraint(model, x, MOI.LessThan(1.0))
@test MOI.is_valid(model, ci)
MOI.delete(model, ci)
@test !MOI.is_valid(model, ci)
return
end

function test_set_constraint_function_constant_not_zero()
model = HiGHS.Optimizer()
x = MOI.add_variable(model)
ci = MOI.add_constraint(model, 1.0 * x, MOI.GreaterThan(0.0))
@test_throws(
MOI.ScalarFunctionConstantNotZero,
MOI.set(model, MOI.ConstraintFunction(), ci, 2.0 * x + 1.0),
)
return
end

function test_delete_integrality()
model = HiGHS.Optimizer()
MOI.set(model, MOI.Silent(), true)
x, _ = MOI.add_constrained_variable(model, MOI.LessThan(1.5))
y, _ = MOI.add_constrained_variable(model, MOI.LessThan(1.5))
ci = MOI.add_constraint(model, y, MOI.Integer())
f = 1.0 * x + 1.0 * y
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.optimize!(model)
@test (MOI.get(model, MOI.ObjectiveValue()), 2.5; atol = 1e-6)
@test MOI.is_valid(model, ci)
MOI.delete(model, ci)
@test !MOI.is_valid(model, ci)
MOI.optimize!(model)
@test (MOI.get(model, MOI.ObjectiveValue()), 3.0; atol = 1e-6)
return
end

function test_copy_to_unsupported_constraint()
src = MOI.Utilities.Model{Float64}()
x = MOI.add_variable(src)
f = MOI.ScalarNonlinearFunction(:log, Any[x])
MOI.add_constraint(src, f, MOI.EqualTo(0.0))
model = HiGHS.Optimizer()
@test_throws(
MOI.UnsupportedConstraint{typeof(f),MOI.EqualTo{Float64}},
MOI.copy_to(model, src),
)
return
end

end # module

TestMOIHighs.runtests()

0 comments on commit b5d5a3f

Please sign in to comment.