diff --git a/Project.toml b/Project.toml index 1a508d0..15b5c9a 100644 --- a/Project.toml +++ b/Project.toml @@ -15,7 +15,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" BinaryProvider = "0.5.9" CEnum = "0.3, 0.4" Clp_jll = "=1.17.6, ~100.1700.600" -MathOptInterface = "0.9.6" +MathOptInterface = "0.10.0" julia = "1" [extras] diff --git a/bench/runbench.jl b/bench/runbench.jl index 3d0f0d0..c37b064 100644 --- a/bench/runbench.jl +++ b/bench/runbench.jl @@ -36,7 +36,7 @@ function generate_moi_problem(model, At, b, c; else for row in 1:rows MOI.add_constraint(model, MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(1, + [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(A_vals[i], x[A_cols[i]]) ) for i in nzrange(At, row)], [-b[row]]), MOI.Nonpositives(1)) @@ -112,7 +112,7 @@ function time_build_and_solve(to_build, to_solve, At, b, c, scalar = true) end @time @timeit "opt" MOI.optimize!(to_solve) MOI.get(to_solve, MOI.ObjectiveValue()) - val = MOI.get(to_solve, MOI.SolveTime()) + val = MOI.get(to_solve, MOI.SolveTimeSec()) println(val) end @@ -145,4 +145,4 @@ function solve_clp(seed, data; time_limit_sec=Inf) end -solve_clp(10, RandomLP(10000, 20000, 0.01); time_limit_sec=5) \ No newline at end of file +solve_clp(10, RandomLP(10000, 20000, 0.01); time_limit_sec=5) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 169b348..cc8c68c 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -3,7 +3,30 @@ import SparseArrays const MOI = MathOptInterface -# Supported scalar sets +MOI.Utilities.@product_of_sets( + _LPProductOfSets, + MOI.EqualTo{T}, + MOI.LessThan{T}, + MOI.GreaterThan{T}, + MOI.Interval{T}, +) + +const OptimizerCache = MOI.Utilities.GenericModel{ + Float64, + MOI.Utilities.ObjectiveContainer{Float64}, + MOI.Utilities.VariablesContainer{Float64}, + MOI.Utilities.MatrixOfConstraints{ + Float64, + MOI.Utilities.MutableSparseMatrixCSC{ + Float64, + Cint, + MOI.Utilities.ZeroBasedIndexing, + }, + MOI.Utilities.Hyperrectangle{Float64}, + _LPProductOfSets{Float64}, + }, +} + const SCALAR_SETS = Union{ MOI.GreaterThan{Float64}, MOI.LessThan{Float64}, @@ -25,15 +48,17 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Create a new Optimizer object. - Set optimizer attributes using `MOI.RawParameter` or + Set optimizer attributes using `MOI.RawOptimizerAttribute` or `JuMP.set_optimizer_atttribute`. For a list of supported parameter names, see `Clp.SUPPORTED_PARAMETERS`. ## Example - using JuMP, Clp - model = JuMP.Model(Clp.Optimizer) - set_optimizer_attribute(model, "LogLevel", 0) + ```julia + using JuMP, Clp + model = JuMP.Model(Clp.Optimizer) + set_optimizer_attribute(model, "LogLevel", 0) + ``` """ function Optimizer(; kwargs...) model = @@ -41,18 +66,19 @@ mutable struct Optimizer <: MOI.AbstractOptimizer if length(kwargs) > 0 @warn("""Passing optimizer attributes as keyword arguments to Clp.Optimizer is deprecated. Use - MOI.set(model, MOI.RawParameter("key"), value) + MOI.set(model, MOI.RawOptimizerAttribute("key"), value) or JuMP.set_optimizer_attribute(model, "key", value) instead. """) end for (key, value) in kwargs - MOI.set(model, MOI.RawParameter(String(key)), value) + MOI.set(model, MOI.RawOptimizerAttribute(String(key)), value) end finalizer(model) do m Clp_deleteModel(m) - return ClpSolve_delete(m.solver_options) + ClpSolve_delete(m.solver_options) + return end return model end @@ -75,7 +101,7 @@ end function MOI.empty!(model::Optimizer) # Copy parameters from old model into new model old_options = Dict( - key => MOI.get(model, MOI.RawParameter(key)) for + key => MOI.get(model, MOI.RawOptimizerAttribute(key)) for key in model.options_set ) empty!(model.options_set) @@ -84,7 +110,7 @@ function MOI.empty!(model::Optimizer) model.optimize_called = false model.solve_time = 0.0 for (key, value) in old_options - MOI.set(model, MOI.RawParameter(key), value) + MOI.set(model, MOI.RawOptimizerAttribute(key), value) end # Work-around for maximumSeconds Clp_setMaximumSeconds(model, model.maximumSeconds) @@ -111,11 +137,11 @@ const SUPPORTED_PARAMETERS = ( "InfeasibleReturn", ) -function MOI.supports(::Optimizer, param::MOI.RawParameter) +function MOI.supports(::Optimizer, param::MOI.RawOptimizerAttribute) return param.name in SUPPORTED_PARAMETERS end -function MOI.set(model::Optimizer, param::MOI.RawParameter, value) +function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value) name = String(param.name) push!(model.options_set, name) if name == "PrimalTolerance" @@ -148,7 +174,7 @@ function MOI.set(model::Optimizer, param::MOI.RawParameter, value) return end -function MOI.get(model::Optimizer, param::MOI.RawParameter) +function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute) name = String(param.name) if name == "PrimalTolerance" return Clp_primalTolerance(model) @@ -175,21 +201,19 @@ function MOI.get(model::Optimizer, param::MOI.RawParameter) return ClpSolve_getSolveType(model.solver_options) elseif name == "InfeasibleReturn" return ClpSolve_infeasibleReturn(model.solver_options) - else - throw(MOI.UnsupportedAttribute(param)) end + return throw(MOI.UnsupportedAttribute(param)) end MOI.supports(::Optimizer, ::MOI.Silent) = true + function MOI.set(model::Optimizer, ::MOI.Silent, value::Bool) push!(model.options_set, "LogLevel") Clp_setLogLevel(model, value ? 0 : 1) return end -function MOI.get(model::Optimizer, ::MOI.Silent) - return Clp_logLevel(model) == 0 -end +MOI.get(model::Optimizer, ::MOI.Silent) = Clp_logLevel(model) == 0 MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true @@ -215,15 +239,7 @@ MOI.supports(::Optimizer, ::MOI.NumberOfThreads) = false function MOI.supports_constraint( ::Optimizer, - ::Type{MOI.ScalarAffineFunction{Float64}}, - ::Type{<:SCALAR_SETS}, -) - return true -end - -function MOI.supports_constraint( - ::Optimizer, - ::Type{MOI.SingleVariable}, + ::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{Float64}}}, ::Type{<:SCALAR_SETS}, ) return true @@ -242,149 +258,73 @@ end # `copy_to` function # ======================= -function _add_bounds(::Vector{Float64}, ub, i, s::MOI.LessThan{Float64}) - return ub[i] = s.upper -end -function _add_bounds(lb, ::Vector{Float64}, i, s::MOI.GreaterThan{Float64}) - return lb[i] = s.lower -end -function _add_bounds(lb, ub, i, s::MOI.EqualTo{Float64}) - return lb[i], ub[i] = s.value, s.value -end -function _add_bounds(lb, ub, i, s::MOI.Interval{Float64}) - return lb[i], ub[i] = s.lower, s.upper -end - -function _extract_bound_data(src, mapping, lb, ub, ::Type{S}) where {S} - for con_index in - MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable,S}()) - f = MOI.get(src, MOI.ConstraintFunction(), con_index) - s = MOI.get(src, MOI.ConstraintSet(), con_index) - column = mapping.varmap[f.variable].value - _add_bounds(lb, ub, column, s) - mapping.conmap[con_index] = - MOI.ConstraintIndex{MOI.SingleVariable,S}(column) - end -end - -function _copy_to_columns(dest::Optimizer, src, mapping) - x_src = MOI.get(src, MOI.ListOfVariableIndices()) - N = Cint(length(x_src)) - for i in 1:N - mapping.varmap[x_src[i]] = MOI.VariableIndex(i) - end - - fobj = - MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) - c = fill(0.0, N) - for term in fobj.terms - i = mapping.varmap[term.variable_index].value - c[i] += term.coefficient +function _index_map( + src::OptimizerCache, + index_map, + ::Type{F}, + ::Type{S}, +) where {F,S} + for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) + row = MOI.Utilities.rows(src.constraints, ci) + index_map[ci] = MOI.ConstraintIndex{F,S}(row) end - # Clp seems to negates the objective offset - Clp_setObjectiveOffset(dest, -fobj.constant) - return N, c -end - -_bounds(s::MOI.GreaterThan{Float64}) = (s.lower, Inf) -_bounds(s::MOI.LessThan{Float64}) = (-Inf, s.upper) -_bounds(s::MOI.EqualTo{Float64}) = (s.value, s.value) -_bounds(s::MOI.Interval{Float64}) = (s.lower, s.upper) - -function add_sizehint!(vec, n) - len = length(vec) - return sizehint!(vec, len + n) + return end -function _extract_row_data(src, mapping, lb, ub, I, J, V, ::Type{S}) where {S} - row = length(I) == 0 ? 1 : I[end] + 1 - list = MOI.get( - src, - MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},S}(), - ) - add_sizehint!(lb, length(list)) - add_sizehint!(ub, length(list)) - n_terms = 0 - fs = Array{MOI.ScalarAffineFunction{Float64}}(undef, length(list)) - for (i, c_index) in enumerate(list) - f = MOI.get(src, MOI.ConstraintFunction(), c_index) - fs[i] = f - l, u = _bounds(MOI.get(src, MOI.ConstraintSet(), c_index)) - push!(lb, l - f.constant) - push!(ub, u - f.constant) - n_terms += length(f.terms) - end - add_sizehint!(I, n_terms) - add_sizehint!(J, n_terms) - add_sizehint!(V, n_terms) - for (i, c_index) in enumerate(list) - f = fs[i]#MOI.get(src, MOI.ConstraintFunction(), c_index) - for term in f.terms - push!(I, row) - push!(J, Cint(mapping.varmap[term.variable_index].value)) - push!(V, term.coefficient) - end - mapping.conmap[c_index] = - MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}(row) - row += 1 +function _index_map( + src::OptimizerCache, + index_map, + F::Type{MOI.VariableIndex}, + ::Type{S}, +) where {S} + for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) + col = index_map[MOI.VariableIndex(ci.value)].value + index_map[ci] = MOI.ConstraintIndex{F,S}(col) end return end -function test_data(src, dest) - for (F, S) in MOI.get(src, MOI.ListOfConstraints()) - if !MOI.supports_constraint(dest, F, S) - throw( - MOI.UnsupportedConstraint{F,S}( - "Clp.Optimizer does not support constraints of type $F-in-$S.", - ), - ) - end +""" + _index_map(src::OptimizerCache) + +Create an `IndexMap` mapping the variables and constraints in `OptimizerCache` +to their corresponding 1-based columns and rows. +""" +function _index_map(src::OptimizerCache) + index_map = MOI.IndexMap() + for (i, x) in enumerate(MOI.get(src, MOI.ListOfVariableIndices())) + index_map[x] = MOI.VariableIndex(i) end - fobj_type = MOI.get(src, MOI.ObjectiveFunctionType()) - if !MOI.supports(dest, MOI.ObjectiveFunction{fobj_type}()) - throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction(fobj_type))) + for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) + _index_map(src, index_map, F, S) end + return index_map end -function MOI.copy_to( - dest::Optimizer, - src::MOI.ModelLike; - copy_names::Bool = false, -) +function MOI.copy_to(dest::Optimizer, src::OptimizerCache) @assert MOI.is_empty(dest) - test_data(src, dest) - - mapping = MOI.Utilities.IndexMap() - N, c = _copy_to_columns(dest, src, mapping) - cl, cu = fill(-Inf, N), fill(Inf, N) - rl, ru, I, J, V = Float64[], Float64[], Cint[], Cint[], Float64[] - - _extract_bound_data(src, mapping, cl, cu, MOI.GreaterThan{Float64}) - _extract_row_data(src, mapping, rl, ru, I, J, V, MOI.GreaterThan{Float64}) - _extract_bound_data(src, mapping, cl, cu, MOI.LessThan{Float64}) - _extract_row_data(src, mapping, rl, ru, I, J, V, MOI.LessThan{Float64}) - _extract_bound_data(src, mapping, cl, cu, MOI.EqualTo{Float64}) - _extract_row_data(src, mapping, rl, ru, I, J, V, MOI.EqualTo{Float64}) - _extract_bound_data(src, mapping, cl, cu, MOI.Interval{Float64}) - _extract_row_data(src, mapping, rl, ru, I, J, V, MOI.Interval{Float64}) - - M = Cint(length(rl)) - A = SparseArrays.sparse(I, J, V, M, N) + A = src.constraints.coefficients + row_bounds = src.constraints.constants + obj = + MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + c = zeros(A.n) + for term in obj.terms + c[term.variable.value] += term.coefficient + end + Clp_setObjectiveOffset(dest, -obj.constant) Clp_loadProblem( dest, A.n, A.m, - A.colptr .- Cint(1), - A.rowval .- Cint(1), + A.colptr, + A.rowval, A.nzval, - cl, - cu, + src.variables.lower, + src.variables.upper, c, - rl, - ru, + row_bounds.lower, + row_bounds.upper, ) - sense = MOI.get(src, MOI.ObjectiveSense()) if sense == MOI.MIN_SENSE Clp_setObjSense(dest, 1) @@ -394,7 +334,28 @@ function MOI.copy_to( @assert sense == MOI.FEASIBILITY_SENSE Clp_setObjSense(dest, 0) end - return mapping + return _index_map(src) +end + +function MOI.copy_to( + dest::Optimizer, + src::MOI.Utilities.UniversalFallback{OptimizerCache}, +) + return MOI.copy_to(dest, src.model) +end + +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) + cache = OptimizerCache() + src_cache = MOI.copy_to(cache, src) + cache_dest = MOI.copy_to(dest, cache) + index_map = MOI.IndexMap() + for (src_x, cache_x) in src_cache.var_map + index_map[src_x] = cache_dest[cache_x] + end + for (src_ci, cache_ci) in src_cache.con_map + index_map[src_ci] = cache_dest[cache_ci] + end + return index_map end # =============================== @@ -409,13 +370,9 @@ function MOI.optimize!(model::Optimizer) return end -function MOI.get(model::Optimizer, ::MOI.SolveTime) - return model.solve_time -end +MOI.get(model::Optimizer, ::MOI.SolveTimeSec) = model.solve_time -function MOI.get(model::Optimizer, ::MOI.NumberOfVariables) - return Clp_getNumCols(model) -end +MOI.get(model::Optimizer, ::MOI.NumberOfVariables) = Clp_getNumCols(model) function MOI.get(model::Optimizer, attr::MOI.ObjectiveValue) MOI.check_result_index_bounds(model, attr) @@ -436,9 +393,8 @@ function MOI.get(model::Optimizer, ::MOI.TerminationStatus) elseif st == 3 # No more granular information that "some limit is reached" return MOI.OTHER_LIMIT - else - return MOI.OTHER_ERROR end + return MOI.OTHER_ERROR end function MOI.get(model::Optimizer, ::MOI.RawStatusString) @@ -454,10 +410,9 @@ function MOI.get(model::Optimizer, ::MOI.RawStatusString) return "2 - dual infeasible" elseif st == 3 return "3 - stopped on iterations etc" - elseif st == 4 - return "4 - stopped due to errors" else - error("Expected integer in [0, 4] but got $st") + @assert st == 4 + return "4 - stopped due to errors" end end @@ -475,27 +430,25 @@ function MOI.get(model::Optimizer, ::MOI.ResultCount) end function MOI.get(model::Optimizer, attr::MOI.PrimalStatus) - if attr.N != 1 + if attr.result_index != 1 return MOI.NO_SOLUTION elseif Clp_isProvenDualInfeasible(model) != 0 return MOI.INFEASIBILITY_CERTIFICATE elseif Clp_primalFeasible(model) != 0 return MOI.FEASIBLE_POINT - else - return MOI.UNKNOWN_RESULT_STATUS end + return MOI.UNKNOWN_RESULT_STATUS end function MOI.get(model::Optimizer, attr::MOI.DualStatus) - if attr.N != 1 + if attr.result_index != 1 return MOI.NO_SOLUTION elseif Clp_isProvenPrimalInfeasible(model) != 0 return MOI.INFEASIBILITY_CERTIFICATE elseif Clp_dualFeasible(model) != 0 return MOI.FEASIBLE_POINT - else - return MOI.UNKNOWN_RESULT_STATUS end + return MOI.UNKNOWN_RESULT_STATUS end # =================== @@ -540,9 +493,8 @@ function MOI.get( Clp_getNumCols(model), x.value, ) - else - error("Primal solution not available") end + return error("Primal solution not available") end function MOI.get( @@ -569,9 +521,8 @@ function MOI.get( Clp_getNumCols(model), col_indices, ) - else - error("Primal solution not available") end + return error("Primal solution not available") end # TODO: What happens if model is unbounded / infeasible? @@ -593,7 +544,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintPrimal, - c::MOI.ConstraintIndex{MOI.SingleVariable,<:SCALAR_SETS}, + c::MOI.ConstraintIndex{MOI.VariableIndex,<:SCALAR_SETS}, ) MOI.check_result_index_bounds(model, attr) return MOI.get(model, MOI.VariablePrimal(), MOI.VariableIndex(c.value)) @@ -652,15 +603,14 @@ function MOI.get( c.value; own = true, ) - else - error("Dual solution not available") end + return error("Dual solution not available") end function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}, ) MOI.check_result_index_bounds(model, attr) if MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE @@ -678,7 +628,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}, ) MOI.check_result_index_bounds(model, attr) if MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE @@ -697,7 +647,7 @@ function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, c::MOI.ConstraintIndex{ - MOI.SingleVariable, + MOI.VariableIndex, <:Union{MOI.Interval{Float64},MOI.EqualTo{Float64}}, }, ) @@ -752,21 +702,15 @@ function MOI.get( status = _CLP_BASIS_STATUS[code] if status == MOI.NONBASIC_AT_UPPER || status == MOI.NONBASIC_AT_LOWER return _nonbasic_status(status, S) - else - return status end + return status end function MOI.get( model::Optimizer, - ::MOI.ConstraintBasisStatus, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, -) where {S} - code = Clp_getColumnStatus(model, c.value - 1) - status = _CLP_BASIS_STATUS[code] - if status == MOI.NONBASIC_AT_UPPER || status == MOI.NONBASIC_AT_LOWER - return _nonbasic_status(status, S) - else - return status - end + ::MOI.VariableBasisStatus, + vi::MOI.VariableIndex, +) + code = Clp_getColumnStatus(model, vi.value - 1) + return _CLP_BASIS_STATUS[code] end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 1a05d70..6639f71 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -6,125 +6,103 @@ import Clp const MOI = MathOptInterface -const OPTIMIZER = Clp.Optimizer() -MOI.set(OPTIMIZER, MOI.Silent(), true) - -const CACHED = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - OPTIMIZER, -) - -const BRIDGED = MOI.Bridges.full_bridge_optimizer(CACHED, Float64) - -const CONFIG = MOI.Test.TestConfig(dual_objective_value = false, basis = true) +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end +end function test_SolverName() - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "Clp" + @test MOI.get(Clp.Optimizer(), MOI.SolverName()) == "Clp" + return end function test_supports_default_copy_to() - @test !MOI.Utilities.supports_allocate_load(OPTIMIZER, false) - @test !MOI.Utilities.supports_allocate_load(OPTIMIZER, true) - @test !MOI.Utilities.supports_default_copy_to(OPTIMIZER, false) - @test !MOI.Utilities.supports_default_copy_to(OPTIMIZER, true) + @test !MOI.supports_incremental_interface(Clp.Optimizer()) + return end -function test_basicconstraint() - return MOI.Test.basic_constraint_tests(CACHED, CONFIG) -end - -function test_unittest() - return MOI.Test.unittest( - BRIDGED, - CONFIG, - [ - # Not supported by upstream. - "number_threads", - - # Tests that require integer variables - "solve_integer_edge_cases", - "solve_zero_one_with_bounds_1", - "solve_zero_one_with_bounds_2", - "solve_zero_one_with_bounds_3", - "solve_objbound_edge_cases", - - # Tests that require quadratic objective / constraints - "solve_qcp_edge_cases", - "solve_qp_edge_cases", - "delete_soc_variables", +function test_runtests() + model = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + Clp.Optimizer(), + ), + Float64, + ) + MOI.set(model, MOI.Silent(), true) + MOI.Test.runtests( + model, + MOI.Test.Config( + exclude = Any[MOI.DualObjectiveValue, MOI.ObjectiveBound], + ), + exclude = [ + # TODO(odow): bug in Clp.jl + "test_model_copy_to_UnsupportedAttribute", + "test_model_ModelFilter_AbstractConstraintAttribute", + # Unable to prove infeasibility + "test_conic_NormInfinityCone_INFEASIBLE", + "test_conic_NormOneCone_INFEASIBLE", ], ) -end - -function test_contlinear() - return MOI.Test.contlineartest(BRIDGED, CONFIG, [ - # MOI.VariablePrimalStart not supported. - "partial_start", - ]) -end - -function test_nametest() - return MOI.Test.nametest(BRIDGED) -end - -function test_validtest() - return MOI.Test.validtest(BRIDGED) -end - -function test_emptytest() - return MOI.Test.emptytest(BRIDGED) + return end function test_Nonexistant_unbounded_ray() - MOI.empty!(BRIDGED) - x = MOI.add_variables(BRIDGED, 5) + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + Clp.Optimizer(), + ) + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 5) MOI.set( - BRIDGED, + model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), ) - MOI.set(BRIDGED, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.optimize!(BRIDGED) - status = MOI.get(BRIDGED, MOI.TerminationStatus()) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.optimize!(model) + status = MOI.get(model, MOI.TerminationStatus()) @test status == MOI.DUAL_INFEASIBLE + return end -function test_RawParameter() +function test_RawOptimizerAttribute() model = Clp.Optimizer() - MOI.set(model, MOI.RawParameter("LogLevel"), 1) - @test MOI.get(model, MOI.RawParameter("LogLevel")) == 1 - @test MOI.get(model, MOI.RawParameter(:LogLevel)) == 1 - MOI.set(model, MOI.RawParameter(:LogLevel), 2) - @test MOI.get(model, MOI.RawParameter("LogLevel")) == 2 - @test MOI.get(model, MOI.RawParameter(:LogLevel)) == 2 + MOI.set(model, MOI.RawOptimizerAttribute("LogLevel"), 1) + @test MOI.get(model, MOI.RawOptimizerAttribute("LogLevel")) == 1 + MOI.set(model, MOI.RawOptimizerAttribute("LogLevel"), 2) + @test MOI.get(model, MOI.RawOptimizerAttribute("LogLevel")) == 2 - MOI.set(model, MOI.RawParameter("SolveType"), 1) - @test MOI.get(model, MOI.RawParameter("SolveType")) == 1 - @test MOI.get(model, MOI.RawParameter(:SolveType)) == 1 - MOI.set(model, MOI.RawParameter("SolveType"), 4) - @test MOI.get(model, MOI.RawParameter("SolveType")) == 4 - @test MOI.get(model, MOI.RawParameter(:SolveType)) == 4 + MOI.set(model, MOI.RawOptimizerAttribute("SolveType"), 1) + @test MOI.get(model, MOI.RawOptimizerAttribute("SolveType")) == 1 + MOI.set(model, MOI.RawOptimizerAttribute("SolveType"), 4) + @test MOI.get(model, MOI.RawOptimizerAttribute("SolveType")) == 4 - MOI.set(model, MOI.RawParameter("PresolveType"), 1) - @test MOI.get(model, MOI.RawParameter("PresolveType")) == 1 - @test MOI.get(model, MOI.RawParameter(:PresolveType)) == 1 - MOI.set(model, MOI.RawParameter("PresolveType"), 0) - @test MOI.get(model, MOI.RawParameter("PresolveType")) == 0 - @test MOI.get(model, MOI.RawParameter(:PresolveType)) == 0 + MOI.set(model, MOI.RawOptimizerAttribute("PresolveType"), 1) + @test MOI.get(model, MOI.RawOptimizerAttribute("PresolveType")) == 1 + MOI.set(model, MOI.RawOptimizerAttribute("PresolveType"), 0) + @test MOI.get(model, MOI.RawOptimizerAttribute("PresolveType")) == 0 + return end function test_All_parameters() model = Clp.Optimizer() - param = MOI.RawParameter("NotAnOption") + param = MOI.RawOptimizerAttribute("NotAnOption") @test !MOI.supports(model, param) @test_throws MOI.UnsupportedAttribute(param) MOI.get(model, param) @test_throws MOI.UnsupportedAttribute(param) MOI.set(model, param, false) for key in Clp.SUPPORTED_PARAMETERS - @test MOI.supports(model, MOI.RawParameter(key)) - value = MOI.get(model, MOI.RawParameter(key)) - MOI.set(model, MOI.RawParameter(key), value) - @test MOI.get(model, MOI.RawParameter(key)) == value + @test MOI.supports(model, MOI.RawOptimizerAttribute(key)) + value = MOI.get(model, MOI.RawOptimizerAttribute(key)) + MOI.set(model, MOI.RawOptimizerAttribute(key), value) + @test MOI.get(model, MOI.RawOptimizerAttribute(key)) == value end + return end function test_copy_to_bug() @@ -140,6 +118,7 @@ function test_copy_to_bug() clp = Clp.Optimizer() index_map = MOI.copy_to(clp, model) @test index_map[con[1]] != index_map[con[2]] + return end function test_options_after_empty!() @@ -149,195 +128,9 @@ function test_options_after_empty!() @test MOI.get(model, MOI.Silent()) == true MOI.empty!(model) @test MOI.get(model, MOI.Silent()) == true -end - -function test_farkas_dual_min() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.SingleVariable}(), - MOI.SingleVariable(x[1]), - ) - clb = - MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.GreaterThan(0.0)) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] > 1e-6 - @test clb_dual[2] > 1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ -2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ -c_dual atol = 1e-6 -end - -function test_farkas_dual_min_interval() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.SingleVariable}(), - MOI.SingleVariable(x[1]), - ) - clb = - MOI.add_constraint.( - model, - MOI.SingleVariable.(x), - MOI.Interval(0.0, 10.0), - ) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] > 1e-6 - @test clb_dual[2] > 1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ -2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ -c_dual atol = 1e-6 -end - -function test_farkas_dual_min_equalto() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.SingleVariable}(), - MOI.SingleVariable(x[1]), - ) - clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.EqualTo(0.0)) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] > 1e-6 - @test clb_dual[2] > 1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ -2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ -c_dual atol = 1e-6 -end - -function test_farkas_dual_min_ii() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x[1])], 0.0), - ) - clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.LessThan(0.0)) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] < -1e-6 - @test clb_dual[2] < -1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ 2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ c_dual atol = 1e-6 -end - -function test_farkas_dual_max() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.SingleVariable}(), - MOI.SingleVariable(x[1]), - ) - clb = - MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.GreaterThan(0.0)) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] > 1e-6 - @test clb_dual[2] > 1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ -2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ -c_dual atol = 1e-6 -end - -function test_farkas_dual_max_ii() - MOI.empty!(BRIDGED) - model = BRIDGED - MOI.set(model, MOI.Silent(), true) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x[1])], 0.0), - ) - clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.LessThan(0.0)) - c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), - MOI.LessThan(-1.0), - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @show clb_dual, c_dual - @test clb_dual[1] < -1e-6 - @test clb_dual[2] < -1e-6 - @test c_dual[1] < -1e-6 - @test clb_dual[1] ≈ 2 * c_dual atol = 1e-6 - @test clb_dual[2] ≈ c_dual atol = 1e-6 + return end end # module TestMOIWrapper -runtests(TestMOIWrapper) +TestMOIWrapper.runtests() diff --git a/test/runtests.jl b/test/runtests.jl index 6db7034..b9206bb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,16 +1,5 @@ using Test -function runtests(mod) - for name in names(mod; all = true) - if !startswith("$(name)", "test_") - continue - end - @testset "$(name)" begin - getfield(mod, name)() - end - end -end - @testset "MathOptInterface" begin include("MOI_wrapper.jl") end