From bab95bf175980b0c52e677ac01c394883d9e009b Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 11 Aug 2021 16:47:39 +1200 Subject: [PATCH] Various updates. Still broken --- src/MOI_wrapper.jl | 251 ++++++++-------------- test/MOI_wrapper.jl | 493 ++++++++------------------------------------ 2 files changed, 170 insertions(+), 574 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 6d7f22f..4ee40b9 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -112,13 +112,6 @@ mutable struct _VariableInfo upper::Float64 # Track integrality type::_TypeEnum - # We can perform an optimization and only store two strings for the - # constraint names because, at most, there can be two SingleVariable - # constraints, e.g., LessThan, GreaterThan. - lessthan_name::String - greaterthan_interval_or_equalto_name::String - integrality_name::String - function _VariableInfo( index::MOI.VariableIndex, column::Cint, @@ -132,9 +125,6 @@ mutable struct _VariableInfo -Inf, Inf, _TYPE_CONTINUOUS, - "", - "", - "", ) end end @@ -181,8 +171,8 @@ end function _variable_info_dict() return CleverDicts.CleverDict{MOI.VariableIndex,_VariableInfo}( - x::MOI.VariableIndex -> x.value, - x::Int64 -> MOI.VariableIndex(x), + x -> x.value, + i -> MOI.VariableIndex(i), ) end @@ -213,8 +203,8 @@ end function _constraint_info_dict() return CleverDicts.CleverDict{_ConstraintKey,_ConstraintInfo}( - x::_ConstraintKey -> x.value, - x::Int64 -> _ConstraintKey(x), + c -> c.value, + i -> _ConstraintKey(i), ) end @@ -237,6 +227,7 @@ function _set(c::_ConstraintInfo) end @enum(_OptimizeStatus, _OPTIMIZE_NOT_CALLED, _OPTIMIZE_OK, _OPTIMIZE_ERRORED) + """ _Solution @@ -302,6 +293,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # MIN_SENSE or MAX_SENSE. This allows us to differentiate between MIN_SENSE # and FEASIBILITY_SENSE. is_feasibility::Bool + is_objective_function_set::Bool + is_objective_sense_set::Bool # HiGHS doesn't have special support for binary variables. Cache them here # to modify bounds on solve. @@ -335,6 +328,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Dict{String,Any}(), "", true, + false, + false, Set{_VariableInfo}(), _variable_info_dict(), _constraint_info_dict(), @@ -375,15 +370,20 @@ function MOI.empty!(model::Optimizer) end model.inner = Highs_create() for (key, value) in model.options - MOI.set(model, MOI.RawParameter(key), value) + MOI.set(model, MOI.RawOptimizerAttribute(key), value) end + model.name = "" model.is_feasibility = true + model.is_objective_function_set = false + model.is_objective_sense_set = false empty!(model.binaries) empty!(model.variable_info) empty!(model.affine_constraint_info) model.name_to_variable = nothing model.name_to_constraint_index = nothing empty!(model.solution) + ret = Highs_changeObjectiveOffset(model, 0.0) + _check_ret(ret) return end @@ -394,6 +394,8 @@ function MOI.is_empty(model::Optimizer) return Highs_getNumCols(model) == 0 && Highs_getNumRows(model) == 0 && model.is_feasibility && + !model.is_objective_function_set && + !model.is_objective_sense_set && isempty(model.binaries) && isempty(model.variable_info) && isempty(model.affine_constraint_info) && @@ -412,18 +414,31 @@ function MOI.get(::Optimizer, ::MOI.ListOfVariableAttributesSet) end function MOI.get(model::Optimizer, ::MOI.ListOfModelAttributesSet) - attributes = [ - MOI.ObjectiveSense(), - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - ] - if MOI.get(model, MOI.Name()) != "" + attributes = MOI.AbstractModelAttribute[] + if model.is_objective_sense_set + push!(attributes, MOI.ObjectiveSense()) + end + if model.is_objective_function_set + push!( + attributes, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) + end + if !isempty(model.name) push!(attributes, MOI.Name()) end return attributes end -function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet) - return MOI.AbstractConstraintAttribute[MOI.ConstraintName()] +function MOI.get( + ::Optimizer, + ::MOI.ListOfConstraintAttributesSet{F,S}, +) where {F,S} + attributes = MOI.AbstractConstraintAttribute[] + if F != MOI.SingleVariable + return push!(attributes, MOI.ConstraintName()) + end + return attributes end function MOI.get(model::Optimizer, ::MOI.NumberOfConstraints{F,S}) where {F,S} @@ -432,7 +447,7 @@ function MOI.get(model::Optimizer, ::MOI.NumberOfConstraints{F,S}) where {F,S} end function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) - constraints = Set{Tuple{DataType,DataType}}() + constraints = Set{Tuple{Type,Type}}() for info in values(model.variable_info) if info.bound == _BOUND_NONE elseif info.bound == _BOUND_LESS_THAN @@ -582,11 +597,11 @@ end MOI.supports(::Optimizer, ::MOI.Silent) = true function MOI.get(model::Optimizer, ::MOI.Silent) - return !MOI.get(model, MOI.RawParameter("output_flag")) + return !MOI.get(model, MOI.RawOptimizerAttribute("output_flag")) end function MOI.set(model::Optimizer, ::MOI.Silent, flag::Bool) - MOI.set(model, MOI.RawParameter("output_flag"), !flag) + MOI.set(model, MOI.RawOptimizerAttribute("output_flag"), !flag) return end @@ -842,6 +857,7 @@ function MOI.modify( ) ret = Highs_changeObjectiveOffset(model, chg.new_constant) _check_ret(ret) + model.is_objective_function_set = true return end @@ -856,6 +872,7 @@ function MOI.modify( chg.new_coefficient, ) _check_ret(ret) + model.is_objective_function_set = true return end @@ -1036,7 +1053,6 @@ function MOI.delete( else info.bound = _BOUND_NONE end - info.lessthan_name = "" model.name_to_constraint_index = nothing return end @@ -1055,7 +1071,6 @@ function MOI.delete( else info.bound = _BOUND_NONE end - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end @@ -1070,7 +1085,6 @@ function MOI.delete( _check_ret(ret) info.lower, info.upper = -Inf, Inf info.bound = _BOUND_NONE - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end @@ -1085,7 +1099,6 @@ function MOI.delete( _check_ret(ret) info.lower, info.upper = -Inf, Inf info.bound = _BOUND_NONE - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end @@ -1156,42 +1169,11 @@ end function MOI.supports( ::Optimizer, ::MOI.ConstraintName, - ::Type{<:MOI.ConstraintIndex}, -) + ::Type{MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}}, +) where {S} return true end -function MOI.get( - model::Optimizer, - ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, -) where {S<:_SCALAR_SETS} - MOI.throw_if_not_valid(model, c) - info = _info(model, c) - if S <: MOI.LessThan - return info.lessthan_name - else - return info.greaterthan_interval_or_equalto_name - end -end - -function MOI.set( - model::Optimizer, - ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, - name::String, -) where {S<:_SCALAR_SETS} - MOI.throw_if_not_valid(model, c) - info = _info(model, c) - if S <: MOI.LessThan - info.lessthan_name = name - else - info.greaterthan_interval_or_equalto_name = name - end - model.name_to_constraint_index = nothing - return -end - ### ### ScalarAffineFunction-in-Set ### @@ -1458,34 +1440,6 @@ function _rebuild_name_to_constraint_index(model::Optimizer) MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}(key.value), ) end - for (key, info) in model.variable_info - if !isempty(info.lessthan_name) - _set_name_to_constraint_index( - model, - info.lessthan_name, - MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}( - key.value, - ), - ) - end - if !isempty(info.greaterthan_interval_or_equalto_name) - S = if info.bound == _BOUND_GREATER_THAN - MOI.GreaterThan{Float64} - elseif info.bound == _BOUND_LESS_AND_GREATER_THAN - MOI.GreaterThan{Float64} - elseif info.bound == _BOUND_EQUAL_TO - MOI.EqualTo{Float64} - else - @assert info.bound == _BOUND_INTERVAL - MOI.Interval{Float64} - end - _set_name_to_constraint_index( - model, - info.greaterthan_interval_or_equalto_name, - MOI.ConstraintIndex{MOI.SingleVariable,S}(key.value), - ) - end - end return end @@ -1897,33 +1851,27 @@ function MOI.get( return MOI.BASIC elseif stat == kUpper return _nonbasic_status(stat, S) - elseif stat == kZero || stat == kNonbasic - return MOI.NONBASIC - else - @assert stat == SUPER - return MOI.SUPER_BASIC end + @assert stat == kZero || stat == kNonbasic + return MOI.NONBASIC end function MOI.get( model::Optimizer, - attr::MOI.ConstraintBasisStatus, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, -) where {S<:_SCALAR_SETS} + attr::MOI.VariableBasisStatus, + x::MOI.VariableIndex, +) MOI.check_result_index_bounds(model, attr) - stat = HighsBasisStatus(model.solution.colstatus[column(model, c)+1]) + stat = HighsBasisStatus(model.solution.colstatus[column(model, x)+1]) if stat == kLower - return _nonbasic_status(stat, S) + return MOI.NONBASIC_AT_LOWER elseif stat == kBasic return MOI.BASIC elseif stat == kUpper - return _nonbasic_status(stat, S) - elseif stat == kZero || stat == kNonbasic - return MOI.NONBASIC - else - @assert stat == SUPER - return MOI.SUPER_BASIC + return MOI.NONBASIC_AT_UPPER end + @assert stat == kZero || stat == kNonbasic + return MOI.NONBASIC end ### @@ -1931,11 +1879,12 @@ end ### function MOI.supports_constraint( - ::Optimizer, + model::Optimizer, ::Type{MOI.SingleVariable}, ::Type{<:Union{MOI.ZeroOne,MOI.Integer}}, ) - return true + solver = MOI.get(model, MOI.RawOptimizerAttribute("solver")) + return solver != "simplex" && solver != "ipm" end function MOI.is_valid( @@ -2040,34 +1989,6 @@ function MOI.delete( return end -function MOI.set( - model::Optimizer, - ::MOI.ConstraintName, - ci::MOI.ConstraintIndex{ - MOI.SingleVariable, - <:Union{MOI.Integer,MOI.ZeroOne}, - }, - name::String, -) - MOI.throw_if_not_valid(model, ci) - info = _info(model, ci) - info.integrality_name = name - return -end - -function MOI.get( - model::Optimizer, - ::MOI.ConstraintName, - ci::MOI.ConstraintIndex{ - MOI.SingleVariable, - <:Union{MOI.Integer,MOI.ZeroOne}, - }, -) - MOI.throw_if_not_valid(model, ci) - info = _info(model, ci) - return info.integrality_name -end - ### ### MOI.copy_to ### @@ -2092,6 +2013,8 @@ function _add_bounds(lb, ub, i, s::MOI.Interval{Float64}) return end +_constraints(src, F, S) = MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) + function _extract_bound_data( dest::Optimizer, src::MOI.ModelLike, @@ -2100,8 +2023,7 @@ function _extract_bound_data( colupper::Vector{Float64}, ::Type{S}, ) where {S} - for c_index in - MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable,S}()) + for c_index in _constraints(src, MOI.SingleVariable, S) f = MOI.get(src, MOI.ConstraintFunction(), c_index) s = MOI.get(src, MOI.ConstraintSet(), c_index) new_f = mapping[f.variable] @@ -2117,16 +2039,16 @@ end function _copy_to_columns(dest::Optimizer, src::MOI.ModelLike, mapping) x_src = MOI.get(src, MOI.ListOfVariableIndices()) numcols = Cint(length(x_src)) - for i in 1:numcols + for (i, x) in enumerate(x_src) index = CleverDicts.add_item( dest.variable_info, _VariableInfo(MOI.VariableIndex(0), Cint(0)), ) info = _info(dest, index) - info.name = MOI.get(dest, MOI.VariableName(), x_src[i]) + info.name = MOI.get(dest, MOI.VariableName(), x) info.index = index info.column = Cint(i - 1) - mapping[x_src[i]] = index + mapping[x] = index end fobj = MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) @@ -2152,10 +2074,7 @@ function _extract_row_data( ::Type{S}, ) where {S} row = length(I) == 0 ? 1 : I[end] + 1 - list = MOI.get( - src, - MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},S}(), - ) + list = _constraints(src, MOI.ScalarAffineFunction{Float64}, S) numrows = length(list) _add_sizehint!(rowlower, numrows) _add_sizehint!(rowupper, numrows) @@ -2214,18 +2133,9 @@ MOI.supports_incremental_interface(::Optimizer, ::Bool) = true function MOI.copy_to( dest::Optimizer, src::MOI.ModelLike; - copy_names::Bool = false, kwargs..., ) - if copy_names - return MOI.Utilities.automatic_copy_to( - dest, - src; - copy_names = true, - kwargs..., - ) - end - @assert MOI.is_empty(dest) + MOI.empty!(dest) _check_input_data(dest, src) mapping = MOI.Utilities.IndexMap() numcol, colcost, offset = _copy_to_columns(dest, src, mapping) @@ -2243,32 +2153,30 @@ function MOI.copy_to( end numrow = Cint(length(rowlower)) A = SparseArrays.sparse(I, J, V, numrow, numcol) + # Extract integrality constraints integrality = fill(kContinuous, numcol) - for ci in MOI.get( - src, - MOI.ListOfConstraintIndices{MOI.SingleVariable,MOI.ZeroOne}(), - ) + for ci in _constraints(src, MOI.SingleVariable, MOI.ZeroOne) + integrality[_info(dest, ci).column+1] = kInteger new_x = mapping[MOI.VariableIndex(ci.value)] - integrality[_info(dest, new_x).column+1] = kInteger mapping[ci] = typeof(ci)(new_x.value) push!(dest.binaries, _info(dest, mapping[ci])) end - for ci in MOI.get( - src, - MOI.ListOfConstraintIndices{MOI.SingleVariable,MOI.Integer}(), - ) + for ci in _constraints(src, MOI.SingleVariable, MOI.Integer) + integrality[_info(dest, ci).column+1] = kInteger new_x = mapping[MOI.VariableIndex(ci.value)] - integrality[_info(dest, new_x).column+1] = kInteger mapping[ci] = typeof(ci)(new_x.value) end - ret = Highs_passMip( + # Build the model + is_max = MOI.get(src, MOI.ObjectiveSense()) == MOI.MAX_SENSE + ret = Highs_passModel( dest, numcol, numrow, - length(V), - kColwise, # The A matrix is given is column-wise. - MOI.get(src, MOI.ObjectiveSense()) == MOI.MAX_SENSE ? kMaximize : - kMinimize, + length(A.nzval), + 0, # q_num_nz, + kColwise, # a_format, + 0, # q_format, + is_max ? kMaximize : kMinimize, offset, colcost, collower, @@ -2278,6 +2186,9 @@ function MOI.copy_to( A.colptr .- Cint(1), A.rowval .- Cint(1), A.nzval, + Cint[0], # qstart, + Cint[], # qindex, + Cdouble[], # qvalue, integrality, ) _check_ret(ret) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index ce0a463..17795a9 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -6,107 +6,32 @@ using Test const MOI = HiGHS.MOI function runtests() - config = Dict( - "simplex" => MOI.Test.TestConfig(basis = true), - "ipm" => - MOI.Test.TestConfig(basis = true, infeas_certificates = false), - "mip" => MOI.Test.TestConfig(infeas_certificates = false), - ) for name in names(@__MODULE__; all = true) - if !startswith("$name", "test_") - continue - end - s = startswith("$name", "test_int_") ? ["mip"] : ["simplex", "ipm"] - @testset "$name-$solver" for solver in s - model = optimizer(solver = solver) - getfield(@__MODULE__, name)(model, config[solver]) + if startswith("$name", "test_") + @testset "$name" begin + getfield(@__MODULE__, name)() + end end end return end -function test_basic_constraint_tests(model, config) - return MOI.Test.basic_constraint_tests(model, config) -end - -function test_unittest(model, config) - return MOI.Test.unittest( - model, - config, - String[ - # TODO(odow): - # Add support for MOI.NumberOfThreads. - "number_threads", - - # These are excluded because HiGHS does not support them. - "delete_soc_variables", - "solve_qcp_edge_cases", - "solve_qp_edge_cases", - "solve_integer_edge_cases", - "solve_objbound_edge_cases", - "solve_zero_one_with_bounds_1", - "solve_zero_one_with_bounds_2", - "solve_zero_one_with_bounds_3", - ], - ) -end - -function test_int_unittest(model, config) - return MOI.Test.unittest( - model, - config, - String[ - # TODO(odow): - # Add support for MOI.NumberOfThreads. - "number_threads", - - # These are excluded because HiGHS does not support them. - "delete_soc_variables", - "solve_qcp_edge_cases", - "solve_qp_edge_cases", - ], - ) -end - -function test_modificationtest(model, config) - return MOI.Test.modificationtest(model, config) -end - -function test_contlineartest(model, config) - excludes = [ - # VariablePrimalStart not supported. - "partial_start", - ] - if MOI.get(model, MOI.RawParameter("solver")) == "ipm" - # TODO(odow): investigate - push!(excludes, "linear8b") - push!(excludes, "linear8c") - end - return MOI.Test.contlineartest(model, config, excludes) -end +const _EXCLUDE = [ + # TODO(odow): bugs in MOI + "test_model_LowerBoundAlreadySet", + "test_model_UpperBoundAlreadySet", + "test_constraint_ConstraintPrimalStart", + "test_constraint_ConstraintDualStart", +] -function test_lintest(model, config) - return MOI.Test.lintest(model, config) -end - -function test_int_lineartest(model, config) - MOI.Test.intlineartest( - model, - config, - # Exclude SOS constraints - [ - "int2", - "indicator1", - "indicator2", - "indicator3", - "indicator4", - "semiconttest", - ], - ) +function test_runtests() + model = MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64) + MOI.set(model, MOI.Silent(), true) + MOI.Test.runtests(model, MOI.Test.Config(), exclude = _EXCLUDE) return end -function test_int_lineartest_cache(::Any, config) +function test_runtests_cache() model = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), @@ -115,115 +40,89 @@ function test_int_lineartest_cache(::Any, config) Float64, ) MOI.set(model, MOI.Silent(), true) - MOI.Test.intlineartest( + MOI.Test.runtests( model, - config, - # Exclude SOS constraints - ["int2", "indicator1", "indicator2", "indicator3", "indicator4"], + MOI.Test.Config(), + exclude = vcat( + _EXCLUDE, + "test_conic_NormInfinityCone_VectorAffineFunction", + "test_conic_NormInfinityCone_VectorOfVariables", + "test_conic_NormOneCone_VectorAffineFunction", + "test_conic_NormOneCone_VectorOfVariables", + ) ) return end -function test_SolverName(model, ::Any) - @test MOI.get(model, MOI.SolverName()) == "HiGHS" -end - -function test_default_objective(model, ::Any) - return MOI.Test.default_objective_test(model) -end - -function test_default_status_test(model, ::Any) - return MOI.Test.default_status_test(model) -end - -function test_nametest(model, ::Any) - return MOI.Test.nametest(model) -end - -function test_validtest(model, ::Any) - return MOI.Test.validtest(model) -end - -function test_emptytest(model, ::Any) - return MOI.Test.emptytest(model) -end - -function test_orderedindicestest(model, ::Any) - return MOI.Test.orderedindicestest(model) -end - -function test_copytest(model, ::Any) - return MOI.Test.copytest( - model, - MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64), - ) +function test_runtests_simplex() + model = MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("solver"), "simplex") + for presolve in ("on", "off") + MOI.set(model, MOI.RawOptimizerAttribute("presolve"), presolve) + MOI.Test.runtests(model, MOI.Test.Config(), exclude = _EXCLUDE) + end + return end -function test_scalar_function_constant_not_zero(model, ::Any) - return MOI.Test.scalar_function_constant_not_zero(model) +function test_runtests_ipm() + model = MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("solver"), "ipm") + MOI.Test.runtests(model, MOI.Test.Config(), exclude = _EXCLUDE) end -function test_supports_constrainttest(model, ::Any) - # supports_constrainttest needs VectorOfVariables-in-Zeros, - # MOI.Test.supports_constrainttest(HiGHS.Optimizer(), Float64, Float32) - # but supports_constrainttest is broken via bridges: - MOI.empty!(model) - MOI.add_variable(model) - @test MOI.supports_constraint( - model, - MOI.SingleVariable, - MOI.EqualTo{Float64}, - ) - @test MOI.supports_constraint( - model, - MOI.ScalarAffineFunction{Float64}, - MOI.EqualTo{Float64}, - ) - # This test is broken for some reason: - @test_broken !MOI.supports_constraint( - model, - MOI.ScalarAffineFunction{Int}, - MOI.EqualTo{Float64}, - ) - @test !MOI.supports_constraint( - model, - MOI.ScalarAffineFunction{Int}, - MOI.EqualTo{Int}, - ) - @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{Int}) - @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) - @test !MOI.supports_constraint( - model, - MOI.VectorOfVariables, - MOI.EqualTo{Float64}, - ) - @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.Zeros) - @test !MOI.supports_constraint( +function test_runtests_ipm_no_presolve() + model = MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("solver"), "ipm") + MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off") + MOI.Test.runtests( model, - MOI.VectorOfVariables, - MOI.Test.UnknownVectorSet, + MOI.Test.Config(), + exclude = vcat( + _EXCLUDE, + ["test_conic_linear_INFEASIBLE", "test_conic_linear_INFEASIBLE_2"], + ), ) + return end -function test_set_lower_bound_twice(::Any, ::Any) - return MOI.Test.set_lower_bound_twice(HiGHS.Optimizer(), Float64) -end - -function test_set_upper_bound_twice(::Any, ::Any) - return MOI.Test.set_upper_bound_twice(HiGHS.Optimizer(), Float64) -end +# function test_int_lineartest_cache(::Any, config) +# model = MOI.Bridges.full_bridge_optimizer( +# MOI.Utilities.CachingOptimizer( +# MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), +# HiGHS.Optimizer(), +# ), +# Float64, +# ) +# MOI.set(model, MOI.Silent(), true) +# MOI.Test.intlineartest( +# model, +# config, +# # Exclude SOS constraints +# ["int2", "indicator1", "indicator2", "indicator3", "indicator4"], +# ) +# return +# end + +function test_SolverName() + @test MOI.get(HiGHS.Optimizer(), MOI.SolverName()) == "HiGHS" + return + end -function test_Attributes(::Any, ::Any) +function test_attributes() model = HiGHS.Optimizer() @test MOI.get(model, MOI.SolverName()) == "HiGHS" @test MOI.get(model, MOI.TimeLimitSec()) > 10000 MOI.set(model, MOI.TimeLimitSec(), 500) @test MOI.get(model, MOI.TimeLimitSec()) == 500.0 @test MOI.get(model, MOI.RawSolver()) == model + return end -function test_MOI_variable_count_and_empty(model, ::Any) - MOI.empty!(model) +function test_MOI_variable_count_and_empty() + model = HiGHS.Optimizer() @test MOI.get(model, MOI.NumberOfVariables()) == 0 x1 = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 1 @@ -238,188 +137,15 @@ function test_MOI_variable_count_and_empty(model, ::Any) @test MOI.get(model, MOI.NumberOfVariables()) == 0 end -function test_Getting_objective_value(model, ::Any) - MOI.empty!(model) - MOI.set(model, MOI.Silent(), true) - (x, _) = MOI.add_constrained_variable(model, MOI.Interval(-3.0, 6.0)) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), - ) - @test MOI.get(model, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE - @test MOI.get(model, MOI.ResultCount()) == 0 - MOI.optimize!(model) - @test MOI.get(model, MOI.ResultCount()) == 1 - @test MOI.get(model, MOI.ObjectiveValue()) ≈ -3 -end - -function test_Max_in_box(model, ::Any) - MOI.empty!(model) - MOI.set(model, MOI.Silent(), true) - @test MOI.get(model, MOI.ResultCount()) == 0 - (x, _) = MOI.add_constrained_variable(model, MOI.Interval(-3.0, 6.0)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 0.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2 * 6 - obj_func = MOI.get( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - ) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL - @test obj_func ≈ - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 0.0) -end - -function test_Objective_function_obtained_from_model_corresponds(model, ::Any) - MOI.empty!(model) - (x1, _) = MOI.add_constrained_variable(model, MOI.Interval(-3.0, 6.0)) - (x2, _) = MOI.add_constrained_variable(model, MOI.Interval(1.0, 2.0)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction( - MOI.ScalarAffineTerm.([2.0, -1.0], [x1, x2]), - 0.0, - ), - ) - F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test F <: MOI.ScalarAffineFunction{Float64} - obj_func = MOI.get(model, MOI.ObjectiveFunction{F}()) - @test MOI.supports(model, MOI.ObjectiveFunction{F}()) - @test all(MOI.get(model, MOI.ListOfVariableIndices()) .== [x1, x2]) - @test obj_func ≈ MOI.ScalarAffineFunction( - [MOI.ScalarAffineTerm(2.0, x1), MOI.ScalarAffineTerm(-1.0, x2)], - 0.0, - ) - MOI.set(model, MOI.ObjectiveFunction{F}(), obj_func) - obj_func = MOI.get(model, MOI.ObjectiveFunction{F}()) - @test obj_func ≈ MOI.ScalarAffineFunction( - [MOI.ScalarAffineTerm(2.0, x1), MOI.ScalarAffineTerm(-1.0, x2)], - 0.0, - ) - obj_func.terms[1] = MOI.ScalarAffineTerm(3.0, x1) - MOI.set(model, MOI.ObjectiveFunction{F}(), obj_func) - obj_func = MOI.get(model, MOI.ObjectiveFunction{F}()) - @test obj_func ≈ MOI.ScalarAffineFunction( - [MOI.ScalarAffineTerm(3.0, x1), MOI.ScalarAffineTerm(-1.0, x2)], - 0.0, - ) -end - -function test_Constrained_variable_equivalent_to_add_constraint(model, ::Any) - MOI.empty!(model) - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variable(model) - _ = MOI.add_constraint( - model, - MOI.SingleVariable(x), - MOI.Interval(-3.0, 6.0), - ) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - @test MOI.get(model, MOI.ResultCount()) == 0 - MOI.optimize!(model) - @test MOI.get(model, MOI.ResultCount()) == 1 - @test MOI.get(model, MOI.ObjectiveValue()) ≈ -3 -end - -function test_Constant_in_objective_function(model, ::Any) - MOI.empty!(model) - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variable(model) - _ = MOI.add_constraint( - model, - MOI.SingleVariable(x), - MOI.Interval(-3.0, 6.0), - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - obj_func = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 3.0) - MOI.set(model, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) - MOI.optimize!(model) - @test MOI.get(model, MOI.ResultCount()) == 1 - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0 - obj_func = MOI.get(model, MOI.ObjectiveFunction{typeof(obj_func)}()) - @test MOI.constant(obj_func) ≈ 3 - MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) - obj_func = MOI.get(model, MOI.ObjectiveFunction{typeof(obj_func)}()) - @test MOI.constant(obj_func) ≈ 0 - @test isempty(obj_func.terms) -end - -function test_Linear_constraints(model, ::Any) - MOI.empty!(model) - # max x1 + 2x2 - # st 0 <= x{1,2} <= 5 - # 0 <= x1 + x2 <= 7.5 - MOI.set(model, MOI.Silent(), true) - (x1, _) = MOI.add_constrained_variable(model, MOI.Interval(0.0, 5.0)) - (x2, _) = MOI.add_constrained_variable(model, MOI.Interval(0.0, 5.0)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - func = MOI.ScalarAffineFunction( - [MOI.ScalarAffineTerm(1.0, x1), MOI.ScalarAffineTerm(2.0, x2)], - 0.0, - ) - @test MOI.supports_constraint(model, typeof(func), MOI.Interval{Float64}) - MOI.set(model, MOI.ObjectiveFunction{typeof(func)}(), func) - @test MOI.get( - model, - MOI.NumberOfConstraints{ - MOI.ScalarAffineFunction{Float64}, - MOI.Interval{Float64}, - }(), - ) == 0 - MOI.add_constraint( - model, - MOI.ScalarAffineFunction( - [MOI.ScalarAffineTerm(1.0, x1), MOI.ScalarAffineTerm(1.0, x2)], - 0.0, - ), - MOI.Interval(0.0, 7.5), - ) - @test MOI.get( - model, - MOI.NumberOfConstraints{ - MOI.ScalarAffineFunction{Float64}, - MOI.Interval{Float64}, - }(), - ) == 1 - MOI.optimize!(model) - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 12.5 -end - -function test_Variable_names(model, config) - MOI.empty!(model) - MOI.Test.variablenames(model, config) - MOI.empty!(model) - y = MOI.add_variable(model) - MOI.set(model, MOI.VariableName(), y, "y") - y2 = MOI.get(model, MOI.VariableIndex, "y") - @test y == y2 - @test MOI.get(model, MOI.VariableIndex, "y0") === nothing -end - -function test_HiGHS_custom_options(::Any, ::Any) +function test_HiGHS_custom_options() model = HiGHS.Optimizer() @test MOI.supports(model, MOI.RawOptimizerAttribute("solver")) @test MOI.get(model, MOI.RawOptimizerAttribute("solver")) == "choose" MOI.set(model, MOI.RawOptimizerAttribute("solver"), "simplex") @test MOI.get(model, MOI.RawOptimizerAttribute("solver")) == "simplex" - @test MOI.get(model, MOI.RawParameter("output_flag")) == true - MOI.set(model, MOI.RawParameter("output_flag"), false) - @test MOI.get(model, MOI.RawParameter("output_flag")) == false + @test MOI.get(model, MOI.RawOptimizerAttribute("output_flag")) == true + MOI.set(model, MOI.RawOptimizerAttribute("output_flag"), false) + @test MOI.get(model, MOI.RawOptimizerAttribute("output_flag")) == false @test MOI.get(model, MOI.RawOptimizerAttribute("time_limit")) > 1000 MOI.set(model, MOI.RawOptimizerAttribute("time_limit"), 1000.0) @test MOI.get(model, MOI.RawOptimizerAttribute("time_limit")) == 1000.0 @@ -438,45 +164,14 @@ function test_HiGHS_custom_options(::Any, ::Any) return end -function test_Model_empty(model, ::Any) - MOI.empty!(model) - @test MOI.is_empty(model) - MOI.add_variable(model) - @test !MOI.is_empty(model) - MOI.empty!(model) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction{Float64}([], 0.0), - ) - @test MOI.is_empty(model) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction{Float64}([], 3.0), - ) - @test !MOI.is_empty(model) - MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) - @test MOI.is_empty(model) - @test MOI.get( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - ) ≈ MOI.ScalarAffineFunction{Float64}([], 0.0) - x = MOI.add_variable(model) - return MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction{Float64}([MOI.ScalarAffineTerm(1.0, x)], 0.0), - ) -end - -function test_show(::Any, ::Any) +function test_show() model = HiGHS.Optimizer() @test sprint(show, model) == "A HiGHS model with 0 columns and 0 rows." return end -function test_options(model, ::Any) +function test_options() + model = HiGHS.Optimizer() options = [ "write_solution_to_file", # Bool "simplex_strategy", # Cint @@ -492,7 +187,8 @@ function test_options(model, ::Any) return end -function test_option_invalid(model, ::Any) +function test_option_invalid() + model = HiGHS.Optimizer() @test_throws( ErrorException( "Encountered an error in HiGHS: Check the log for details.", @@ -502,7 +198,8 @@ function test_option_invalid(model, ::Any) return end -function test_option_unknown_option(model, ::Any) +function test_option_unknown_option() + model = HiGHS.Optimizer() err = ErrorException( "Encountered an error in HiGHS: Check the log for details.", ) @@ -519,7 +216,7 @@ function test_option_unknown_option(model, ::Any) return end -function test_copy_to(::Any, ::Any) +function test_copy_to() src = MOI.Utilities.Model{Float64}() MOI.Utilities.loadfromstring!( src, @@ -555,18 +252,6 @@ c8: w + x in MathOptInterface.Interval(1.0, 2.0) return end -function optimizer(; solver::String = "") - model = HiGHS.Optimizer() - MOI.set(model, MOI.Silent(), true) - # Turn off presolve for simplex to get infeas_certificates. - if solver == "simplex" || solver == "ipm" - presolve = solver == "simplex" ? "off" : "on" - MOI.set(model, MOI.RawParameter("presolve"), presolve) - MOI.set(model, MOI.RawParameter("solver"), solver) - end - return MOI.Bridges.full_bridge_optimizer(model, Float64) -end - end TestMOIHighs.runtests()