From 08dd98940007a6171673cb0477d85862d214d606 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 7 Sep 2021 15:30:02 +1200 Subject: [PATCH] Breaking changes for MOI 0.10.0 (#178) --- Project.toml | 2 +- perf/runbench.jl | 20 +- src/GLPK.jl | 5 +- src/MOI_wrapper/MOI_copy.jl | 59 +- src/MOI_wrapper/MOI_wrapper.jl | 329 ++++------- src/MOI_wrapper/deprecated_constants.jl | 2 +- src/precompile.jl | 34 +- test/MOI_callbacks.jl | 9 +- test/MOI_wrapper.jl | 709 ++++++------------------ 9 files changed, 351 insertions(+), 818 deletions(-) diff --git a/Project.toml b/Project.toml index 0b52d8b..a3d5611 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" BinaryProvider = "~0.5" CEnum = "0.3, 0.4" GLPK_jll = "=5.0.0" -MathOptInterface = "~0.9.19" +MathOptInterface = "~0.10.0" julia = "1" [extras] diff --git a/perf/runbench.jl b/perf/runbench.jl index 3ff6c32..16c49d5 100644 --- a/perf/runbench.jl +++ b/perf/runbench.jl @@ -22,10 +22,8 @@ function generate_moi_problem(model, At, b, c; A_vals = nonzeros(At) if var_bounds for col in 1:cols - MOI.add_constraint(model, MOI.SingleVariable(x[col]), - MOI.LessThan(10.0)) - MOI.add_constraint(model, MOI.SingleVariable(x[col]), - MOI.GreaterThan(-10.0)) + MOI.add_constraint(model, x[col], MOI.LessThan(10.0)) + MOI.add_constraint(model, x[col], MOI.GreaterThan(-10.0)) end end if scalar @@ -37,7 +35,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)) @@ -113,7 +111,7 @@ function time_build_and_solve(to_build, to_solve, At, b, c, scalar = true) end MOI.set(to_solve, MOI.TimeLimitSec(), 0.0010) @time @timeit "opt" MOI.optimize!(to_solve) - val = MOI.get(to_solve, MOI.SolveTime()) + val = MOI.get(to_solve, MOI.SolveTimeSec()) println(val) @show MOI.get(to_solve, MOI.ObjectiveValue()) @show MOI.get(to_solve, MOI.TerminationStatus()) @@ -129,19 +127,19 @@ function solve_GLPK(seed, data; time_limit_sec=Inf) GC.gc() bridged_cache, pure_solver = bridged_cache_and_solver() @timeit "bc + s" time_build_and_solve(bridged_cache, pure_solver, At, b, c) - + GC.gc() cache, pure_solver2 = cache_and_solver() @timeit "c + s" time_build_and_solve(cache, pure_solver2, At, b, c) - + GC.gc() full_solver = bridged_cached_solver() @timeit "bcs" time_build_and_solve(full_solver, full_solver, At, b, c) - + GC.gc() full_solver = bridged_cached_solver() @timeit "bcs + v" time_build_and_solve(full_solver, full_solver, At, b, c, false) - + GC.gc() cache_solver = cached_solver() @timeit "cs" time_build_and_solve(cache_solver, cache_solver, At, b, c) @@ -153,4 +151,4 @@ function solve_GLPK(seed, data; time_limit_sec=Inf) end solve_GLPK(2, RandomLP(11, 11, 0.5); time_limit_sec=5) -solve_GLPK(20, RandomLP(10000, 10000, 0.005); time_limit_sec=5) \ No newline at end of file +solve_GLPK(20, RandomLP(10000, 10000, 0.005); time_limit_sec=5) diff --git a/src/GLPK.jl b/src/GLPK.jl index 56a5d86..2be8767 100644 --- a/src/GLPK.jl +++ b/src/GLPK.jl @@ -5,7 +5,10 @@ if haskey(ENV, "JULIA_GLPK_LIBRARY_PATH") || VERSION < v"1.3" if isfile(deps_file) include(deps_file) else - error("GLPK not properly installed. Please run import `Pkg; Pkg.build(\"GLPK\")`.") + error( + "GLPK not properly installed. Please run " * + "`import Pkg; Pkg.build(\"GLPK\")`.", + ) end else import GLPK_jll: libglpk diff --git a/src/MOI_wrapper/MOI_copy.jl b/src/MOI_wrapper/MOI_copy.jl index 252eeae..45a67e1 100644 --- a/src/MOI_wrapper/MOI_copy.jl +++ b/src/MOI_wrapper/MOI_copy.jl @@ -37,7 +37,7 @@ Throw an error if unsupported constraint or objective types are present in `src`. """ function _validate_constraint_types(dest::Optimizer, src::MOI.ModelLike) - for (F, S) in MOI.get(src, MOI.ListOfConstraints()) + for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) if !MOI.supports_constraint(dest, F, S) throw( MOI.UnsupportedConstraint{F,S}( @@ -45,17 +45,28 @@ function _validate_constraint_types(dest::Optimizer, src::MOI.ModelLike) ), ) end + for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F,S}()) + if !MOI.supports(dest, attr, MOI.ConstraintIndex{F,S}) + throw(MOI.UnsupportedAttribute(attr)) + end + end + end + for attr in MOI.get(src, MOI.ListOfModelAttributesSet()) + if !MOI.supports(dest, attr) + throw(MOI.UnsupportedAttribute(attr)) + end end - fobj_type = MOI.get(src, MOI.ObjectiveFunctionType()) - if !MOI.supports(dest, MOI.ObjectiveFunction{fobj_type}()) - throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction(fobj_type))) + for attr in MOI.get(src, MOI.ListOfVariableAttributesSet()) + if !MOI.supports(dest, attr, MOI.VariableIndex) + throw(MOI.UnsupportedAttribute(attr)) + end end return end function _init_index_map(src::MOI.ModelLike) variables = MOI.get(src, MOI.ListOfVariableIndices()) - map = MOIU.IndexMap() + map = MOI.Utilities.IndexMap() N = 0 for x in variables N += 1 @@ -82,22 +93,22 @@ function _add_set_data(cache, i, s::MOI.Interval{Float64}) end function _extract_bound_data(src, map, cache, s::Type{S}) where {S} - for ci in MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable,S}()) + for ci in MOI.get(src, MOI.ListOfConstraintIndices{MOI.VariableIndex,S}()) f = MOI.get(src, MOI.ConstraintFunction(), ci) s = MOI.get(src, MOI.ConstraintSet(), ci) - column = map[f.variable].value + column = map[f].value _add_set_data(cache, column, s) - map[ci] = MOI.ConstraintIndex{MOI.SingleVariable,S}(column) + map[ci] = MOI.ConstraintIndex{MOI.VariableIndex,S}(column) end return end function _extract_type_data(src, map, cache, ::Type{S}) where {S} - for ci in MOI.get(src, MOI.ListOfConstraintIndices{MOI.SingleVariable,S}()) + for ci in MOI.get(src, MOI.ListOfConstraintIndices{MOI.VariableIndex,S}()) f = MOI.get(src, MOI.ConstraintFunction(), ci) - column = map[f.variable].value + column = map[f].value cache.types[column] = S == MOI.Integer ? INTEGER : BINARY - map[ci] = MOI.ConstraintIndex{MOI.SingleVariable,S}(column) + map[ci] = MOI.ConstraintIndex{MOI.VariableIndex,S}(column) end return end @@ -110,8 +121,8 @@ function _extract_row_data(src, map, cache, ::Type{S}) where {S} MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},S}(), )::Vector{MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}} f = MOI.get(src, MOI.ConstraintFunction(), ci) - if !MOIU.is_canonical(f) - f = MOIU.canonical(f) + if !MOI.Utilities.is_canonical(f) + f = MOI.Utilities.canonical(f) end l, u = _bounds(MOI.get(src, MOI.ConstraintSet(), ci)) push!(cache.rl, l === nothing ? -Inf : l - f.constant) @@ -122,7 +133,7 @@ function _extract_row_data(src, map, cache, ::Type{S}) where {S} for term in f.terms nnz += 1 cache.I[nnz] = row - cache.J[nnz] = Cint(map[term.variable_index].value::Int64) + cache.J[nnz] = Cint(map[term.variable].value::Int64) cache.V[nnz] = term.coefficient end map[ci] = MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}(row) @@ -137,9 +148,9 @@ function _add_all_variables(model::Optimizer, cache::_OptimizerCache) sizehint!(model.variable_info, N) for i in 1:N bound = get_moi_bound_type(cache.cl[i], cache.cu[i], cache.bounds[i]) - index = CleverDicts.add_item( + CleverDicts.add_item( model.variable_info, - VariableInfo(MOI.VariableIndex(i), i, bound, cache.types[i]), + VariableInfo(MOI.VariableIndex(i), i, bound, cache.types[i], ""), ) glp_bound_type = get_glp_bound_type(cache.cl[i], cache.cu[i]) glp_set_col_bnds(model, i, glp_bound_type, cache.cl[i], cache.cu[i]) @@ -193,12 +204,7 @@ function _add_all_constraints(dest::Optimizer, cache::_OptimizerCache) return end -function MOI.copy_to( - dest::Optimizer, - src::MOI.ModelLike; - copy_names::Bool = false, - kwargs..., -) +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) @assert MOI.is_empty(dest) _validate_constraint_types(dest, src) # Initialize the problem storage @@ -221,12 +227,11 @@ function MOI.copy_to( _add_all_variables(dest, cache) _add_all_constraints(dest, cache) # Copy model attributes: - MOIU.pass_attributes(dest, src, copy_names, map) - MOIU.pass_attributes(dest, src, copy_names, map, variables) - for (F, S) in MOI.get(src, MOI.ListOfConstraints()) + MOI.Utilities.pass_attributes(dest, src, map) + MOI.Utilities.pass_attributes(dest, src, map, variables) + for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) indices = MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) - # TODO(odow): fix copy_names = false. - MOIU.pass_attributes(dest, src, false, map, indices) + MOI.Utilities.pass_attributes(dest, src, map, indices) end return map end diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 983287b..e87797a 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -1,7 +1,6 @@ import MathOptInterface const MOI = MathOptInterface -const MOIU = MOI.Utilities const CleverDicts = MOI.Utilities.CleverDicts @enum(TypeEnum, CONTINUOUS, BINARY, INTEGER) @@ -24,25 +23,10 @@ mutable struct VariableInfo bound::BoundEnum type::TypeEnum name::String - # Storage for constraint names associated with variables because GLPK - # can only store names for variables and proper constraints. - # We can perform an optimization and only store three strings for the - # constraint names because, at most, there can be three SingleVariable - # constraints, e.g., LessThan, GreaterThan, and Integer. - lessthan_name::String - greaterthan_interval_or_equalto_name::String - type_constraint_name::String - function VariableInfo(index::MOI.VariableIndex, column::Int) - return new(index, column, NONE, CONTINUOUS, "", "", "", "") - end - function VariableInfo( - index::MOI.VariableIndex, - column::Int, - bound::BoundEnum, - type::TypeEnum, - ) - return new(index, column, bound, type, "", "", "", "") - end +end + +function VariableInfo(index::MOI.VariableIndex, column::Int) + return VariableInfo(index, column, NONE, CONTINUOUS, "") end struct ConstraintKey @@ -180,16 +164,16 @@ mutable struct Optimizer <: MOI.AbstractOptimizer model.simplex_param = glp_smcp() glp_init_smcp(model.simplex_param) - MOI.set(model, MOI.RawParameter("msg_lev"), GLP_MSG_ERR) + MOI.set(model, MOI.RawOptimizerAttribute("msg_lev"), GLP_MSG_ERR) if length(kwargs) > 0 @warn( "Passing parameters as keyword arguments is deprecated. Use " * - "`JuMP.set_optimizer_attribute` or `MOI.RawParameter(key)` " * + "`JuMP.set_optimizer_attribute` or `MOI.RawOptimizerAttribute(key)` " * "instead." ) end for (key, val) in kwargs - MOI.set(model, MOI.RawParameter(String(key)), val) + MOI.set(model, MOI.RawOptimizerAttribute(String(key)), val) end model.silent = false model.variable_info = @@ -318,7 +302,7 @@ end function MOI.supports_constraint( ::Optimizer, - ::Type{MOI.SingleVariable}, + ::Type{MOI.VariableIndex}, ::Type{F}, ) where { F<:Union{ @@ -355,10 +339,11 @@ const _SCALAR_SETS = Union{ } MOI.supports(::Optimizer, ::MOI.VariableName, ::Type{MOI.VariableIndex}) = true + function MOI.supports( ::Optimizer, ::MOI.ConstraintName, - ::Type{<:MOI.ConstraintIndex}, + ::Type{<:MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}}}, ) return true end @@ -367,7 +352,7 @@ MOI.supports(::Optimizer, ::MOI.Name) = true MOI.supports(::Optimizer, ::MOI.Silent) = true MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true -MOI.supports(::Optimizer, ::MOI.RawParameter) = true +MOI.supports(::Optimizer, ::MOI.RawOptimizerAttribute) = true """ _set_parameter(param_store, key::Symbol, value)::Bool @@ -391,10 +376,7 @@ function _set_parameter(param_store, key::Symbol, value) return false end -function MOI.set(model::Optimizer, param::MOI.RawParameter, value) - if typeof(param.name) != String - error("GLPK.jl requires strings as arguments to `RawParameter`.") - end +function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value) key = Symbol(param.name) set_interior = _set_parameter(model.interior_param, key, value) set_intopt = _set_parameter(model.intopt_param, key, value) @@ -405,10 +387,7 @@ function MOI.set(model::Optimizer, param::MOI.RawParameter, value) return end -function MOI.get(model::Optimizer, param::MOI.RawParameter) - if typeof(param.name) != String - error("GLPK.jl requires strings as arguments to `RawParameter`.") - end +function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute) name = Symbol(param.name) if (model.method == SIMPLEX || model.method == EXACT) && name in fieldnames(glp_smcp) @@ -428,16 +407,16 @@ function MOI.set( ::MOI.TimeLimitSec, limit::Union{Nothing,Real}, ) - MOI.set(model, MOI.RawParameter("tm_lim"), _limit_sec_to_ms(limit)) + MOI.set(model, MOI.RawOptimizerAttribute("tm_lim"), _limit_sec_to_ms(limit)) return end function MOI.get(model::Optimizer, ::MOI.TimeLimitSec) # convert internal ms to sec - return MOI.get(model, MOI.RawParameter("tm_lim")) / 1_000 + return MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) / 1_000 end -MOI.Utilities.supports_default_copy_to(::Optimizer, ::Bool) = true +MOI.supports_incremental_interface(::Optimizer) = true function MOI.get(model::Optimizer, ::MOI.ListOfVariableAttributesSet) return MOI.AbstractVariableAttribute[MOI.VariableName()] @@ -452,10 +431,20 @@ function MOI.get(model::Optimizer, ::MOI.ListOfModelAttributesSet) return attributes end -function MOI.get(model::Optimizer, ::MOI.ListOfConstraintAttributesSet) +function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet) return MOI.AbstractConstraintAttribute[MOI.ConstraintName()] end +function MOI.get( + ::Optimizer, + ::MOI.ListOfConstraintAttributesSet{ + MOI.VariableIndex, + <:MOI.AbstractScalarSet, + }, +) + return MOI.AbstractConstraintAttribute[] +end + function _indices_and_coefficients( indices::Vector{Cint}, coefficients::Vector{Float64}, @@ -464,7 +453,7 @@ function _indices_and_coefficients( ) i = 1 for term in f.terms - indices[i] = Cint(column(model, term.variable_index)) + indices[i] = Cint(column(model, term.variable)) coefficients[i] = term.coefficient i += 1 end @@ -624,6 +613,10 @@ function MOI.set( else @assert sense == MOI.FEASIBILITY_SENSE glp_set_obj_dir(model, GLP_MIN) + for col in Cint(0):Cint(glp_get_num_cols(model) - 1) + glp_set_obj_coef(model, col, 0.0) + end + glp_set_obj_coef(model, 0, 0.0) model.is_feasibility = true end return @@ -657,7 +650,7 @@ function MOI.set( num_vars = length(model.variable_info) obj = zeros(Float64, num_vars) for term in f.terms - col = column(model, term.variable_index) + col = column(model, term.variable) obj[col] += term.coefficient end for (col, coef) in enumerate(obj) @@ -695,12 +688,12 @@ function MOI.modify( end ## -## SingleVariable-in-Set constraints. +## VariableIndex-in-Set constraints. ## function _info( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,<:Any}, + c::MOI.ConstraintIndex{MOI.VariableIndex,<:Any}, ) var_index = MOI.VariableIndex(c.value) if haskey(model.variable_info, var_index) @@ -709,13 +702,13 @@ function _info( return throw(MOI.InvalidIndex(c)) end -function column(model, c::MOI.ConstraintIndex{MOI.SingleVariable,<:Any}) +function column(model, c::MOI.ConstraintIndex{MOI.VariableIndex,<:Any}) return _info(model, c).column end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}, ) if haskey(model.variable_info, MOI.VariableIndex(c.value)) info = _info(model, c) @@ -726,7 +719,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}, ) if haskey(model.variable_info, MOI.VariableIndex(c.value)) info = _info(model, c) @@ -737,7 +730,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Interval{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{Float64}}, ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).bound == INTERVAL @@ -745,7 +738,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.EqualTo{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{Float64}}, ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).bound == EQUAL_TO @@ -753,7 +746,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.ZeroOne}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}, ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).type == BINARY @@ -761,7 +754,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Integer}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Integer}, ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).type == INTEGER @@ -770,19 +763,19 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintFunction, - c::MOI.ConstraintIndex{MOI.SingleVariable,<:Any}, + c::MOI.ConstraintIndex{MOI.VariableIndex,<:Any}, ) MOI.throw_if_not_valid(model, c) - return MOI.SingleVariable(MOI.VariableIndex(c.value)) + return MOI.VariableIndex(c.value) end function MOI.set( - model::Optimizer, + ::Optimizer, ::MOI.ConstraintFunction, - c::MOI.ConstraintIndex{MOI.SingleVariable,<:Any}, - ::MOI.SingleVariable, + c::MOI.ConstraintIndex{MOI.VariableIndex,<:Any}, + ::MOI.VariableIndex, ) - return throw(MOI.SettingSingleVariableFunctionNotAllowed()) + return throw(MOI.SettingVariableIndexNotAllowed()) end _bounds(s::MOI.GreaterThan{Float64}) = (s.lower, nothing) @@ -832,29 +825,29 @@ end function MOI.add_constraint( model::Optimizer, - f::MOI.SingleVariable, + f::MOI.VariableIndex, s::S, ) where {S<:_SCALAR_SETS} - info = _info(model, f.variable) + info = _info(model, f) if S <: MOI.LessThan{Float64} - _throw_if_existing_upper(info.bound, info.type, S, f.variable) + _throw_if_existing_upper(info.bound, info.type, S, f) info.bound = info.bound == GREATER_THAN ? LESS_AND_GREATER_THAN : LESS_THAN elseif S <: MOI.GreaterThan{Float64} - _throw_if_existing_lower(info.bound, info.type, S, f.variable) + _throw_if_existing_lower(info.bound, info.type, S, f) info.bound = info.bound == LESS_THAN ? LESS_AND_GREATER_THAN : GREATER_THAN elseif S <: MOI.EqualTo{Float64} - _throw_if_existing_lower(info.bound, info.type, S, f.variable) - _throw_if_existing_upper(info.bound, info.type, S, f.variable) + _throw_if_existing_lower(info.bound, info.type, S, f) + _throw_if_existing_upper(info.bound, info.type, S, f) info.bound = EQUAL_TO else @assert S <: MOI.Interval{Float64} - _throw_if_existing_lower(info.bound, info.type, S, f.variable) - _throw_if_existing_upper(info.bound, info.type, S, f.variable) + _throw_if_existing_lower(info.bound, info.type, S, f) + _throw_if_existing_upper(info.bound, info.type, S, f) info.bound = INTERVAL end - index = MOI.ConstraintIndex{MOI.SingleVariable,typeof(s)}(f.variable.value) + index = MOI.ConstraintIndex{MOI.VariableIndex,typeof(s)}(f.value) MOI.set(model, MOI.ConstraintSet(), index, s) return index end @@ -903,7 +896,7 @@ end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -913,46 +906,42 @@ function MOI.delete( else info.bound = NONE end - info.lessthan_name = "" model.name_to_constraint_index = nothing return end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) _set_variable_bound(model, info.column, -Inf, nothing) info.bound = info.bound == LESS_AND_GREATER_THAN ? LESS_THAN : NONE - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Interval{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{Float64}}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) _set_variable_bound(model, info.column, -Inf, Inf) info.bound = NONE - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.EqualTo{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{Float64}}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) _set_variable_bound(model, info.column, -Inf, Inf) info.bound = NONE - info.greaterthan_interval_or_equalto_name = "" model.name_to_constraint_index = nothing return end @@ -960,7 +949,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.GreaterThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}, ) MOI.throw_if_not_valid(model, c) lower = glp_get_col_lb(model, column(model, c)) @@ -970,7 +959,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}, ) MOI.throw_if_not_valid(model, c) upper = glp_get_col_ub(model, column(model, c)) @@ -980,7 +969,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.EqualTo{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{Float64}}, ) MOI.throw_if_not_valid(model, c) lower = glp_get_col_lb(model, column(model, c)) @@ -990,7 +979,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Interval{Float64}}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Interval{Float64}}, ) MOI.throw_if_not_valid(model, c) col = column(model, c) @@ -1002,7 +991,7 @@ end function MOI.set( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, + c::MOI.ConstraintIndex{MOI.VariableIndex,S}, s::S, ) where {S<:_SCALAR_SETS} MOI.throw_if_not_valid(model, c) @@ -1013,10 +1002,10 @@ end function MOI.add_constraint( model::Optimizer, - f::MOI.SingleVariable, + f::MOI.VariableIndex, ::MOI.ZeroOne, ) - info = _info(model, f.variable) + info = _info(model, f) # See https://github.com/JuliaOpt/GLPKMathProgInterface.jl/pull/15 # for why this is necesary. GLPK interacts weirdly with binary variables and # bound modification. So let's set binary variables as "Integer" with [0,1] @@ -1024,12 +1013,12 @@ function MOI.add_constraint( glp_set_col_kind(model, info.column, GLP_IV) info.type = BINARY model.num_binaries += 1 - return MOI.ConstraintIndex{MOI.SingleVariable,MOI.ZeroOne}(f.variable.value) + return MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(f.value) end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.ZeroOne}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1043,7 +1032,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.ZeroOne}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}, ) MOI.throw_if_not_valid(model, c) return MOI.ZeroOne() @@ -1051,19 +1040,19 @@ end function MOI.add_constraint( model::Optimizer, - f::MOI.SingleVariable, + f::MOI.VariableIndex, ::MOI.Integer, ) - info = _info(model, f.variable) + info = _info(model, f) glp_set_col_kind(model, info.column, GLP_IV) info.type = INTEGER model.num_integers += 1 - return MOI.ConstraintIndex{MOI.SingleVariable,MOI.Integer}(f.variable.value) + return MOI.ConstraintIndex{MOI.VariableIndex,MOI.Integer}(f.value) end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Integer}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Integer}, ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1077,52 +1066,12 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable,MOI.Integer}, + c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Integer}, ) MOI.throw_if_not_valid(model, c) return MOI.Integer() end -function MOI.get( - model::Optimizer, - ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, -) where {S} - MOI.throw_if_not_valid(model, c) - info = _info(model, c) - if S <: MOI.LessThan - return info.lessthan_name - elseif S <: Union{MOI.GreaterThan,MOI.Interval,MOI.EqualTo} - return info.greaterthan_interval_or_equalto_name - else - @assert S <: Union{MOI.ZeroOne,MOI.Integer} - return info.type_constraint_name - end -end - -function MOI.set( - model::Optimizer, - ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, - name::String, -) where {S} - MOI.throw_if_not_valid(model, c) - info = _info(model, c) - if S <: MOI.LessThan - old_name = info.lessthan_name - info.lessthan_name = name - elseif S <: Union{MOI.GreaterThan,MOI.Interval,MOI.EqualTo} - old_name = info.greaterthan_interval_or_equalto_name - info.greaterthan_interval_or_equalto_name = name - else - @assert S <: Union{MOI.ZeroOne,MOI.Integer} - old_name = info.type_constraint_name - info.type_constraint_name = name - end - model.name_to_constraint_index = nothing - return -end - ### ### ScalarAffineFunction-in-Set ### @@ -1369,47 +1318,6 @@ function _rebuild_name_to_constraint_index(model::Optimizer) ), ) 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 == GREATER_THAN || - info.bound == LESS_AND_GREATER_THAN - MOI.GreaterThan{Float64} - elseif info.bound == EQUAL_TO - MOI.EqualTo{Float64} - else - @assert info.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 - if !isempty(info.type_constraint_name) - S = if info.type == BINARY - MOI.ZeroOne - else - @assert info.type == INTEGER - MOI.Integer - end - _set_name_to_constraint_index( - model, - info.type_constraint_name, - MOI.ConstraintIndex{MOI.SingleVariable,S}(key.value), - ) - end - end return end @@ -1775,7 +1683,7 @@ end function MOI.get(model::Optimizer, attr::MOI.PrimalStatus) _throw_if_optimize_in_progress(model, attr) - if attr.N != 1 + if attr.result_index != 1 return MOI.NO_SOLUTION end (status, _) = _get_status(model) @@ -1795,7 +1703,7 @@ end function MOI.get(model::Optimizer, attr::MOI.DualStatus) _throw_if_optimize_in_progress(model, attr) - if attr.N != 1 || model.last_solved_by_mip + if attr.result_index != 1 || model.last_solved_by_mip return MOI.NO_SOLUTION end (status, _) = _get_status(model) @@ -1858,7 +1766,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintPrimal, - c::MOI.ConstraintIndex{MOI.SingleVariable,<:Any}, + c::MOI.ConstraintIndex{MOI.VariableIndex,<:Any}, ) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -1892,7 +1800,7 @@ 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}}, ) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -1929,7 +1837,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}}, ) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -1966,7 +1874,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable,S}, + c::MOI.ConstraintIndex{MOI.VariableIndex,S}, ) where {S<:Union{MOI.EqualTo,MOI.Interval}} _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -2042,7 +1950,7 @@ function MOI.get(model::Optimizer, attr::MOI.RelativeGap) return model.relative_gap end -function MOI.get(model::Optimizer, attr::MOI.SolveTime) +function MOI.get(model::Optimizer, attr::MOI.SolveTimeSec) _throw_if_optimize_in_progress(model, attr) return model.solve_time end @@ -2067,8 +1975,9 @@ end function MOI.set(model::Optimizer, ::MOI.Silent, flag::Bool) model.silent = flag - output_flag = flag ? GLP_OFF : MOI.get(model, MOI.RawParameter("msg_lev")) - MOI.set(model, MOI.RawParameter("msg_lev"), output_flag) + output_flag = + flag ? GLP_OFF : MOI.get(model, MOI.RawOptimizerAttribute("msg_lev")) + MOI.set(model, MOI.RawOptimizerAttribute("msg_lev"), output_flag) return end @@ -2082,14 +1991,20 @@ function MOI.set(model::Optimizer, ::MOI.Name, name::String) return end -MOI.get(model::Optimizer, ::MOI.NumberOfVariables) = length(model.variable_info) +function MOI.get(model::Optimizer, ::MOI.NumberOfVariables)::Int64 + return length(model.variable_info) +end + function MOI.get(model::Optimizer, ::MOI.ListOfVariableIndices) return sort!(collect(keys(model.variable_info)), by = x -> x.value) end MOI.get(model::Optimizer, ::MOI.RawSolver) = model -function MOI.get(model::Optimizer, ::MOI.NumberOfConstraints{F,S}) where {F,S} +function MOI.get( + model::Optimizer, + ::MOI.NumberOfConstraints{F,S}, +)::Int64 where {F,S} # TODO: this could be more efficient. return length(MOI.get(model, MOI.ListOfConstraintIndices{F,S}())) end @@ -2106,12 +2021,12 @@ _type_enums(::Any) = (nothing,) function MOI.get( model::Optimizer, - ::MOI.ListOfConstraintIndices{MOI.SingleVariable,S}, + ::MOI.ListOfConstraintIndices{MOI.VariableIndex,S}, ) where {S} - indices = MOI.ConstraintIndex{MOI.SingleVariable,S}[] + indices = MOI.ConstraintIndex{MOI.VariableIndex,S}[] for (key, info) in model.variable_info if info.bound in _bound_enums(S) || info.type in _type_enums(S) - push!(indices, MOI.ConstraintIndex{MOI.SingleVariable,S}(key.value)) + push!(indices, MOI.ConstraintIndex{MOI.VariableIndex,S}(key.value)) end end return sort!(indices, by = x -> x.value) @@ -2135,27 +2050,27 @@ function MOI.get( return sort!(indices, by = x -> x.value) end -function MOI.get(model::Optimizer, ::MOI.ListOfConstraints) - constraints = Set{Tuple{DataType,DataType}}() +function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) + constraints = Set{Tuple{Type,Type}}() for info in values(model.variable_info) if info.bound == NONE elseif info.bound == LESS_THAN - push!(constraints, (MOI.SingleVariable, MOI.LessThan{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.LessThan{Float64})) elseif info.bound == GREATER_THAN - push!(constraints, (MOI.SingleVariable, MOI.GreaterThan{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.GreaterThan{Float64})) elseif info.bound == LESS_AND_GREATER_THAN - push!(constraints, (MOI.SingleVariable, MOI.LessThan{Float64})) - push!(constraints, (MOI.SingleVariable, MOI.GreaterThan{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.LessThan{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.GreaterThan{Float64})) elseif info.bound == EQUAL_TO - push!(constraints, (MOI.SingleVariable, MOI.EqualTo{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.EqualTo{Float64})) elseif info.bound == INTERVAL - push!(constraints, (MOI.SingleVariable, MOI.Interval{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.Interval{Float64})) end if info.type == CONTINUOUS elseif info.type == BINARY - push!(constraints, (MOI.SingleVariable, MOI.ZeroOne)) + push!(constraints, (MOI.VariableIndex, MOI.ZeroOne)) elseif info.type == INTEGER - push!(constraints, (MOI.SingleVariable, MOI.Integer)) + push!(constraints, (MOI.VariableIndex, MOI.Integer)) end end for info in values(model.affine_constraint_info) @@ -2252,36 +2167,22 @@ 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, +) _throw_if_optimize_in_progress(model, attr) - col = column(model, c) + col = column(model, x) vbasis = glp_get_col_stat(model, col) if vbasis == GLP_BS return MOI.BASIC elseif vbasis == GLP_NL - if S <: MOI.LessThan - return MOI.BASIC - elseif !(S <: MOI.Interval) - return MOI.NONBASIC - else - return MOI.NONBASIC_AT_LOWER - end + return MOI.NONBASIC_AT_LOWER elseif vbasis == GLP_NU - MOI.NONBASIC_AT_UPPER - if S <: MOI.GreaterThan - return MOI.BASIC - elseif !(S <: MOI.Interval) - return MOI.NONBASIC - else - return MOI.NONBASIC_AT_UPPER - end + return MOI.NONBASIC_AT_UPPER elseif vbasis == GLP_NF return MOI.NONBASIC - elseif vbasis == GLP_NS - return MOI.NONBASIC else - error("VBasis value of $(vbasis) isn't defined.") + @assert vbasis == GLP_NS + return MOI.SUPERBASIC end end diff --git a/src/MOI_wrapper/deprecated_constants.jl b/src/MOI_wrapper/deprecated_constants.jl index 67f1eac..57500da 100644 --- a/src/MOI_wrapper/deprecated_constants.jl +++ b/src/MOI_wrapper/deprecated_constants.jl @@ -13,7 +13,7 @@ end function MOI.set( model::Optimizer, - param::MOI.RawParameter, + param::MOI.RawOptimizerAttribute, value::DeprecatedConstant, ) @warn( diff --git a/src/precompile.jl b/src/precompile.jl index 405ac38..a95f05b 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -8,7 +8,10 @@ function _precompile_() Base.precompile(MOI.delete, (Optimizer, MOI.VariableIndex)) Base.precompile(MOI.get, (Optimizer, MOI.VariablePrimal, MOI.VariableIndex)) - Base.precompile(MOI.set, (Optimizer, MOI.ObjectiveSense, MOI.OptimizationSense)) + Base.precompile( + MOI.set, + (Optimizer, MOI.ObjectiveSense, MOI.OptimizationSense), + ) Base.precompile( MOI.set, ( @@ -18,7 +21,7 @@ function _precompile_() ), ) - functions = (MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable) + functions = (MOI.ScalarAffineFunction{Float64}, MOI.VariableIndex) sets = ( MOI.LessThan{Float64}, MOI.GreaterThan{Float64}, @@ -31,7 +34,10 @@ function _precompile_() MOI.get, (Optimizer, MOI.ConstraintPrimal, MOI.ConstraintIndex{F,S}), ) - Base.precompile(MOI.get, (Optimizer, MOI.ConstraintDual, MOI.ConstraintIndex{F,S})) + Base.precompile( + MOI.get, + (Optimizer, MOI.ConstraintDual, MOI.ConstraintIndex{F,S}), + ) Base.precompile( MOI.get, (Optimizer, MOI.ConstraintFunction, MOI.ConstraintIndex{F,S}), @@ -40,7 +46,10 @@ function _precompile_() MOI.set, (Optimizer, MOI.ConstraintFunction, MOI.ConstraintIndex{F,S}, F), ) - Base.precompile(MOI.get, (Optimizer, MOI.ConstraintSet, MOI.ConstraintIndex{F,S})) + Base.precompile( + MOI.get, + (Optimizer, MOI.ConstraintSet, MOI.ConstraintIndex{F,S}), + ) Base.precompile( MOI.set, (Optimizer, MOI.ConstraintSet, MOI.ConstraintIndex{F,S}, S), @@ -48,8 +57,14 @@ function _precompile_() Base.precompile(MOI.is_valid, (Optimizer, MOI.ConstraintIndex{F,S})) Base.precompile(MOI.delete, (Optimizer, MOI.ConstraintIndex{F,S})) end - Base.precompile(MOI.add_constraint, (Optimizer, MOI.SingleVariable, MOI.ZeroOne)) - Base.precompile(MOI.add_constraint, (Optimizer, MOI.SingleVariable, MOI.Integer)) + Base.precompile( + MOI.add_constraint, + (Optimizer, MOI.VariableIndex, MOI.ZeroOne), + ) + Base.precompile( + MOI.add_constraint, + (Optimizer, MOI.VariableIndex, MOI.Integer), + ) Base.precompile(MOI.optimize!, (Optimizer,)) for attr in ( @@ -83,7 +98,12 @@ function _precompile_() for S in sets Base.precompile( MOI.submit, - (Optimizer, MOI.UserCut{CallbackData}, MOI.ScalarAffineFunction{Float64}, S), + ( + Optimizer, + MOI.UserCut{CallbackData}, + MOI.ScalarAffineFunction{Float64}, + S, + ), ) Base.precompile( MOI.submit, diff --git a/test/MOI_callbacks.jl b/test/MOI_callbacks.jl index 20fbb2d..3a1c550 100644 --- a/test/MOI_callbacks.jl +++ b/test/MOI_callbacks.jl @@ -13,8 +13,11 @@ function runtests() getfield(@__MODULE__, name)() end else - @testset "$(name)" begin - getfield(@__MODULE__, name)(true) + # TODO(odow): this is broken! + # @testset "$(name)_cache" begin + # getfield(@__MODULE__, name)(true) + # end + @testset "$(name)_no_cache" begin getfield(@__MODULE__, name)(false) end end @@ -64,7 +67,7 @@ function _callback_knapsack_model(cache) MOI.set(model, MOI.Silent(), true) N = 30 x = MOI.add_variables(model, N) - MOI.add_constraints(model, MOI.SingleVariable.(x), MOI.ZeroOne()) + MOI.add_constraints(model, x, MOI.ZeroOne()) Random.seed!(1) item_weights, item_values = rand(N), rand(N) MOI.add_constraint( diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 0264075..783025a 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,253 +1,92 @@ -using GLPK, Test +module TestMOIWrapper + +using GLPK +using Test const MOI = GLPK.MathOptInterface -const MOIT = MOI.Test - -const OPTIMIZER = MOI.Bridges.full_bridge_optimizer(GLPK.Optimizer(), Float64) -const CONFIG = MOIT.TestConfig() - -@testset "Unit Tests" begin - MOIT.basic_constraint_tests(OPTIMIZER, CONFIG) - MOIT.unittest( - OPTIMIZER, - CONFIG, - [ - # FIXME `NumberOfThreads` not supported - "number_threads", - # These are excluded because GLPK does not support quadratics. - "solve_qcp_edge_cases", - "solve_qp_edge_cases", - "delete_soc_variables", - - # Tested below because the termination status is different. - "solve_zero_one_with_bounds_3", - - # TODO(odow): not implemented. - "number_threads", - ], - ) - @testset "solve_zero_one_with_bounds_3" begin - MOI.empty!(OPTIMIZER) - MOI.Utilities.loadfromstring!( - OPTIMIZER, - """ - variables: x - maxobjective: 2.0x - c1: x in ZeroOne() - c2: x >= 0.2 - c3: x <= 0.5 -""", - ) - MOI.optimize!(OPTIMIZER) - # We test this here because the TerminationStatus is INVALID_MODEL not - # INFEASIBLE. - @test MOI.get(OPTIMIZER, MOI.TerminationStatus()) == MOI.INVALID_MODEL - end - MOIT.modificationtest(OPTIMIZER, CONFIG) -end -@testset "Linear tests" begin - @testset "Default Solver" begin - MOIT.contlineartest( - OPTIMIZER, - MOIT.TestConfig(basis = true), - [ - # VariablePrimalStart not supported. - "partial_start", - ], - ) - end - @testset "Cached solver for `copy_to`" begin - MOIT.contlineartest( - MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback( - MOI.Utilities.Model{Float64}(), - ), - GLPK.Optimizer(), - ), - Float64, - ), - MOIT.TestConfig(basis = true), - [ - # VariablePrimalStart not supported. - "partial_start", - ], - ) +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end end + return end -@testset "Linear Conic tests" begin - MOIT.lintest(OPTIMIZER, CONFIG) -end - -@testset "Integer Linear tests" begin - MOIT.intlineartest( - OPTIMIZER, - CONFIG, - ["int2", "indicator1", "indicator2", "indicator3", "indicator4"], +# TODO: also run the test for this model: +# MOI.Bridges.full_bridge_optimizer( +# MOI.Utilities.CachingOptimizer( +# MOI.Utilities.UniversalFallback( +# MOI.Utilities.Model{Float64}(), +# ), +# GLPK.Optimizer(), +# ), +# Float64, +# ) +function test_MOI_Test() + MOI.Test.runtests( + MOI.Bridges.full_bridge_optimizer(GLPK.Optimizer(), Float64), + MOI.Test.Config(); + exclude = [ + # GLPK returns INVALID_MODEL instead of INFEASIBLE + "test_constraint_ZeroOne_bounds_3", + # Upstream issue: https://github.com/jump-dev/MathOptInterface.jl/issues/1431 + "test_model_LowerBoundAlreadySet", + "test_model_UpperBoundAlreadySet", + # TODO(odow): bug in GLPK + "test_objective_set_via_modify", + "test_objective_FEASIBILITY_SENSE_clears_objective", + ], ) - @testset "Cached solver for `copy_to`" begin - MOIT.intlineartest( - MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback( - MOI.Utilities.Model{Float64}(), - ), - GLPK.Optimizer(), - ), - Float64, - ), - CONFIG, - ["int2", "indicator1", "indicator2", "indicator3", "indicator4"], - ) - end + return end -@testset "ModelLike tests" begin - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "GLPK" - - @testset "default_objective_test" begin - MOIT.default_objective_test(OPTIMIZER) - end - - @testset "default_status_test" begin - MOIT.default_status_test(OPTIMIZER) - end - - @testset "nametest" begin - MOIT.nametest(OPTIMIZER) - end - - @testset "validtest" begin - MOIT.validtest(OPTIMIZER) - end - - @testset "emptytest" begin - MOIT.emptytest(OPTIMIZER) - end - - @testset "orderedindicestest" begin - MOIT.orderedindicestest(OPTIMIZER) - end - - @testset "copytest" begin - MOIT.copytest( - OPTIMIZER, - MOI.Bridges.full_bridge_optimizer(GLPK.Optimizer(), Float64), - ) - end - - @testset "scalar_function_constant_not_zero" begin - MOIT.scalar_function_constant_not_zero(OPTIMIZER) - end - - @testset "start_values_test" begin - # We don't support ConstraintDualStart or ConstraintPrimalStart yet. - # @test_broken MOIT.start_values_test(GLPK.Optimizer(), OPTIMIZER) - end - - @testset "supports_constrainttest" begin - # supports_constrainttest needs VectorOfVariables-in-Zeros, - # MOIT.supports_constrainttest(GLPK.Optimizer(), Float64, Float32) - # but supports_constrainttest is broken via bridges: - MOI.empty!(OPTIMIZER) - MOI.add_variable(OPTIMIZER) - @test MOI.supports_constraint( - OPTIMIZER, - MOI.SingleVariable, - MOI.EqualTo{Float64}, - ) - @test MOI.supports_constraint( - OPTIMIZER, - MOI.ScalarAffineFunction{Float64}, - MOI.EqualTo{Float64}, - ) - # This test is broken for some reason: - @test_broken !MOI.supports_constraint( - OPTIMIZER, - MOI.ScalarAffineFunction{Int}, - MOI.EqualTo{Float64}, - ) - @test !MOI.supports_constraint( - OPTIMIZER, - MOI.ScalarAffineFunction{Int}, - MOI.EqualTo{Int}, - ) - @test !MOI.supports_constraint( - OPTIMIZER, - MOI.SingleVariable, - MOI.EqualTo{Int}, - ) - @test MOI.supports_constraint( - OPTIMIZER, - MOI.VectorOfVariables, - MOI.Zeros, - ) - @test !MOI.supports_constraint( - OPTIMIZER, - MOI.VectorOfVariables, - MOI.EqualTo{Float64}, - ) - @test !MOI.supports_constraint(OPTIMIZER, MOI.SingleVariable, MOI.Zeros) - @test !MOI.supports_constraint( - OPTIMIZER, - MOI.VectorOfVariables, - MOIT.UnknownVectorSet, - ) - end - - @testset "set_lower_bound_twice" begin - MOIT.set_lower_bound_twice(GLPK.Optimizer(), Float64) - end - - @testset "set_upper_bound_twice" begin - MOIT.set_upper_bound_twice(GLPK.Optimizer(), Float64) - end -end - -@testset "Parameter setting" begin +function test_parameter_setting() solver = GLPK.Optimizer(tm_lim = 1, ord_alg = 2, alien = 3) @test solver.simplex_param.tm_lim == 1 @test solver.intopt_param.tm_lim == 1 @test solver.interior_param.ord_alg == 2 @test solver.intopt_param.alien == 3 + return end -@testset "Issue #79" begin - @testset "An unbounded integer model" begin - model = GLPK.Optimizer() - MOI.Utilities.loadfromstring!( - model, - """ - variables: x, y - minobjective: -5.0x + y - c1: x in Integer() - c2: x in LessThan(1.0) +function test_unbounded_integer_model() + model = GLPK.Optimizer() + MOI.Utilities.loadfromstring!( + model, + """ +variables: x, y +minobjective: -5.0x + y +x in Integer() +x in LessThan(1.0) """, - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE - end + ) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE + return +end - @testset "An infeasible integer model" begin - model = GLPK.Optimizer() - MOI.Utilities.loadfromstring!( - model, - """ - variables: x - minobjective: -5.0x - c1: x in Integer() - c2: x in LessThan(1.0) - c3: 1.0x in GreaterThan(2.0) +function test_infeasible_integer_model() + model = GLPK.Optimizer() + MOI.Utilities.loadfromstring!( + model, + """ +variables: x +minobjective: -5.0x +x in Integer() +x in LessThan(1.0) +c3: 1.0x in GreaterThan(2.0) """, - ) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - end + ) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + return end -@testset "Issue #70" begin +function test_issue_70() model = GLPK.Optimizer() x = MOI.add_variable(model) f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0], [x]), 0.0) @@ -263,31 +102,31 @@ end @test GLPK.glp_get_row_type(model.inner, row) == GLPK.GLP_UP @test GLPK.glp_get_row_lb(model.inner, row) == -GLPK.GLP_DBL_MAX @test GLPK.glp_get_row_ub(model.inner, row) == 1.0 + return end -@testset "Infeasible bounds" begin +function test_infeasible_bounds() model = GLPK.Optimizer() x = MOI.add_variable(model) - MOI.add_constraint(model, MOI.SingleVariable(x), MOI.Interval(1.0, -1.0)) + MOI.add_constraint(model, x, MOI.Interval(1.0, -1.0)) MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.INVALID_MODEL + return end -@testset "RawParameter" begin +function test_RawOptimizerAttribute() model = GLPK.Optimizer(method = GLPK.SIMPLEX) exception = ErrorException( "Invalid option: cb_func. Use the MOI attribute `GLPK.CallbackFunction` instead.", ) @test_throws exception MOI.set( model, - MOI.RawParameter("cb_func"), + MOI.RawOptimizerAttribute("cb_func"), (cb) -> nothing, ) - MOI.set(model, MOI.RawParameter("tm_lim"), 100) - @test MOI.get(model, MOI.RawParameter("tm_lim")) == 100 - @test_throws ErrorException MOI.get(model, MOI.RawParameter(:tm_lim)) - @test_throws ErrorException MOI.set(model, MOI.RawParameter(:tm_lim), 120) - param = MOI.RawParameter("bad") + MOI.set(model, MOI.RawOptimizerAttribute("tm_lim"), 100) + @test MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) == 100 + param = MOI.RawOptimizerAttribute("bad") @test_throws MOI.UnsupportedAttribute(param) MOI.set(model, param, 1) @test_throws MOI.UnsupportedAttribute(param) MOI.get(model, param) @@ -297,19 +136,19 @@ end ) @test_throws exception MOI.set( model, - MOI.RawParameter("cb_func"), + MOI.RawOptimizerAttribute("cb_func"), (cb) -> nothing, ) - MOI.set(model, MOI.RawParameter("tm_lim"), 100) - @test MOI.get(model, MOI.RawParameter("tm_lim")) == 100 + MOI.set(model, MOI.RawOptimizerAttribute("tm_lim"), 100) + @test MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) == 100 @test_throws MOI.UnsupportedAttribute(param) MOI.set( model, - MOI.RawParameter("bad"), + MOI.RawOptimizerAttribute("bad"), 1, ) @test_throws MOI.UnsupportedAttribute(param) MOI.get( model, - MOI.RawParameter("bad"), + MOI.RawOptimizerAttribute("bad"), ) model = GLPK.Optimizer(method = GLPK.EXACT) @@ -318,47 +157,49 @@ end ) @test_throws exception MOI.set( model, - MOI.RawParameter("cb_func"), + MOI.RawOptimizerAttribute("cb_func"), (cb) -> nothing, ) - MOI.set(model, MOI.RawParameter("tm_lim"), 100) - @test MOI.get(model, MOI.RawParameter("tm_lim")) == 100 + MOI.set(model, MOI.RawOptimizerAttribute("tm_lim"), 100) + @test MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) == 100 @test_throws MOI.UnsupportedAttribute(param) MOI.set( model, - MOI.RawParameter("bad"), + MOI.RawOptimizerAttribute("bad"), 1, ) @test_throws MOI.UnsupportedAttribute(param) MOI.get( model, - MOI.RawParameter("bad"), + MOI.RawOptimizerAttribute("bad"), ) model = GLPK.Optimizer() - MOI.set(model, MOI.RawParameter("mip_gap"), 0.001) - @test MOI.get(model, MOI.RawParameter("mip_gap")) == 0.001 + MOI.set(model, MOI.RawOptimizerAttribute("mip_gap"), 0.001) + @test MOI.get(model, MOI.RawOptimizerAttribute("mip_gap")) == 0.001 + return end -@testset "TimeLimitSec issue #110" begin +function test_TimeLimitSec_issue_110() model = GLPK.Optimizer(method = GLPK.SIMPLEX) MOI.set(model, MOI.TimeLimitSec(), nothing) - @test MOI.get(model, MOI.RawParameter("tm_lim")) == typemax(Int32) + @test MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) == typemax(Int32) MOI.set(model, MOI.TimeLimitSec(), 100) - @test MOI.get(model, MOI.RawParameter("tm_lim")) == 100000 + @test MOI.get(model, MOI.RawOptimizerAttribute("tm_lim")) == 100000 @test MOI.get(model, MOI.TimeLimitSec()) == 100 # conversion between ms and sec - MOI.set(model, MOI.RawParameter("tm_lim"), 100) + MOI.set(model, MOI.RawOptimizerAttribute("tm_lim"), 100) @test isapprox(MOI.get(model, MOI.TimeLimitSec()), 0.1) + return end -@testset "RelativeGap" begin +function test_RelativeGap() model = GLPK.Optimizer() MOI.Utilities.loadfromstring!( model, """ variables: x minobjective: 1.0x - c1: x in Integer() - c2: x in GreaterThan(1.5) + x in Integer() + x in GreaterThan(1.5) """, ) MOI.optimize!(model) @@ -370,72 +211,19 @@ end """ variables: x minobjective: 1.0x - c1: x in GreaterThan(1.5) + x in GreaterThan(1.5) """, ) MOI.optimize!(model) @test_throws ErrorException MOI.get(model, MOI.RelativeGap()) + return end -@testset "Extra name tests" begin - model = GLPK.Optimizer() - @testset "Variables" begin - MOI.empty!(model) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.VariableName(), x[1], "x1") - @test MOI.get(model, MOI.VariableIndex, "x1") == x[1] - MOI.set(model, MOI.VariableName(), x[1], "x2") - @test MOI.get(model, MOI.VariableIndex, "x1") === nothing - @test MOI.get(model, MOI.VariableIndex, "x2") == x[1] - MOI.set(model, MOI.VariableName(), x[2], "x1") - @test MOI.get(model, MOI.VariableIndex, "x1") == x[2] - MOI.set(model, MOI.VariableName(), x[1], "x1") - @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x1") - end - - @testset "Variable bounds" begin - MOI.empty!(model) - x = MOI.add_variable(model) - c1 = MOI.add_constraint( - model, - MOI.SingleVariable(x), - MOI.GreaterThan(0.0), - ) - c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) - MOI.set(model, MOI.ConstraintName(), c1, "c1") - @test MOI.get(model, MOI.ConstraintIndex, "c1") == c1 - MOI.set(model, MOI.ConstraintName(), c1, "c2") - @test MOI.get(model, MOI.ConstraintIndex, "c1") === nothing - @test MOI.get(model, MOI.ConstraintIndex, "c2") == c1 - MOI.set(model, MOI.ConstraintName(), c2, "c1") - @test MOI.get(model, MOI.ConstraintIndex, "c1") == c2 - MOI.set(model, MOI.ConstraintName(), c1, "c1") - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "c1") - end - - @testset "Affine constraints" begin - MOI.empty!(model) - x = MOI.add_variable(model) - f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0) - c1 = MOI.add_constraint(model, f, MOI.GreaterThan(0.0)) - c2 = MOI.add_constraint(model, f, MOI.LessThan(1.0)) - MOI.set(model, MOI.ConstraintName(), c1, "c1") - @test MOI.get(model, MOI.ConstraintIndex, "c1") == c1 - MOI.set(model, MOI.ConstraintName(), c1, "c2") - @test MOI.get(model, MOI.ConstraintIndex, "c1") === nothing - @test MOI.get(model, MOI.ConstraintIndex, "c2") == c1 - MOI.set(model, MOI.ConstraintName(), c2, "c1") - @test MOI.get(model, MOI.ConstraintIndex, "c1") == c2 - MOI.set(model, MOI.ConstraintName(), c1, "c1") - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "c1") - end -end - -@testset "Issue #102" begin +function test_issue_102() model = GLPK.Optimizer() x = MOI.add_variable(model) - MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) - MOI.add_constraint(model, MOI.SingleVariable(x), MOI.Integer()) + MOI.add_constraint(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint(model, x, MOI.Integer()) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set( model, @@ -445,9 +233,10 @@ end MOI.optimize!(model) @test MOI.get(model, MOI.ObjectiveValue()) == 3.0 @test MOI.get(model, MOI.ObjectiveBound()) == 3.0 + return end -@testset "Issue #116" begin +function test_issue_116() model = GLPK.Optimizer(method = GLPK.EXACT) x = MOI.add_variables(model, 2) c1 = MOI.add_constraint( @@ -458,8 +247,8 @@ end ), MOI.LessThan(1.0), ) - c2 = MOI.add_constraint(model, MOI.SingleVariable(x[1]), MOI.EqualTo(1.0)) - c3 = MOI.add_constraint(model, MOI.SingleVariable(x[2]), MOI.EqualTo(1.0)) + c2 = MOI.add_constraint(model, x[1], MOI.EqualTo(1.0)) + c3 = MOI.add_constraint(model, x[2], MOI.EqualTo(1.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) MOI.set( model, @@ -473,87 +262,26 @@ end @test cd1 <= 1e-6 @test MOI.get(model, MOI.ConstraintDual(), c2) ≈ -cd1 atol = 1e-6 @test MOI.get(model, MOI.ConstraintDual(), c3) ≈ -cd1 atol = 1e-6 + return end -@testset "Default parameters" begin +function test_default_parameters() model = GLPK.Optimizer() - @test MOI.get(model, MOI.RawParameter("msg_lev")) == GLPK.GLP_MSG_ERR - @test MOI.get(model, MOI.RawParameter("presolve")) == GLPK.GLP_OFF + @test MOI.get(model, MOI.RawOptimizerAttribute("msg_lev")) == + GLPK.GLP_MSG_ERR + @test MOI.get(model, MOI.RawOptimizerAttribute("presolve")) == GLPK.GLP_OFF model = GLPK.Optimizer(msg_lev = GLPK.GLP_MSG_ALL, presolve = true) - @test MOI.get(model, MOI.RawParameter("msg_lev")) == GLPK.GLP_MSG_ALL - @test MOI.get(model, MOI.RawParameter("presolve")) == GLPK.GLP_ON -end - -@testset "Duplicate names" begin - @testset "Variables" begin - model = GLPK.Optimizer() - (x, y, z) = MOI.add_variables(model, 3) - MOI.set(model, MOI.VariableName(), x, "x") - MOI.set(model, MOI.VariableName(), y, "x") - MOI.set(model, MOI.VariableName(), z, "z") - @test MOI.get(model, MOI.VariableIndex, "z") == z - @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x") - MOI.set(model, MOI.VariableName(), y, "y") - @test MOI.get(model, MOI.VariableIndex, "x") == x - @test MOI.get(model, MOI.VariableIndex, "y") == y - MOI.set(model, MOI.VariableName(), z, "x") - @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x") - MOI.delete(model, x) - @test MOI.get(model, MOI.VariableIndex, "x") == z - end - @testset "SingleVariable" begin - model = GLPK.Optimizer() - x = MOI.add_variables(model, 3) - c = MOI.add_constraints( - model, - MOI.SingleVariable.(x), - MOI.GreaterThan(0.0), - ) - MOI.set(model, MOI.ConstraintName(), c[1], "x") - MOI.set(model, MOI.ConstraintName(), c[2], "x") - MOI.set(model, MOI.ConstraintName(), c[3], "z") - @test MOI.get(model, MOI.ConstraintIndex, "z") == c[3] - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x") - MOI.set(model, MOI.ConstraintName(), c[2], "y") - @test MOI.get(model, MOI.ConstraintIndex, "x") == c[1] - @test MOI.get(model, MOI.ConstraintIndex, "y") == c[2] - MOI.set(model, MOI.ConstraintName(), c[3], "x") - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x") - MOI.delete(model, c[1]) - @test MOI.get(model, MOI.ConstraintIndex, "x") == c[3] - MOI.set(model, MOI.ConstraintName(), c[2], "x") - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x") - MOI.delete(model, x[3]) - @test MOI.get(model, MOI.ConstraintIndex, "x") == c[2] - end - @testset "ScalarAffineFunction" begin - model = GLPK.Optimizer() - x = MOI.add_variables(model, 3) - fs = [ - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, xi)], 0.0) - for xi in x - ] - c = MOI.add_constraints(model, fs, MOI.GreaterThan(0.0)) - MOI.set(model, MOI.ConstraintName(), c[1], "x") - MOI.set(model, MOI.ConstraintName(), c[2], "x") - MOI.set(model, MOI.ConstraintName(), c[3], "z") - @test MOI.get(model, MOI.ConstraintIndex, "z") == c[3] - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x") - MOI.set(model, MOI.ConstraintName(), c[2], "y") - @test MOI.get(model, MOI.ConstraintIndex, "x") == c[1] - @test MOI.get(model, MOI.ConstraintIndex, "y") == c[2] - MOI.set(model, MOI.ConstraintName(), c[3], "x") - @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x") - MOI.delete(model, c[1]) - @test MOI.get(model, MOI.ConstraintIndex, "x") == c[3] - end + @test MOI.get(model, MOI.RawOptimizerAttribute("msg_lev")) == + GLPK.GLP_MSG_ALL + @test MOI.get(model, MOI.RawOptimizerAttribute("presolve")) == GLPK.GLP_ON + return end -@testset "Duals with equal bounds" begin +function test_duals_with_equal_bounds() model = GLPK.Optimizer() x = MOI.add_variable(model) - xl = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) - xu = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) + xl = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + xu = MOI.add_constraint(model, x, MOI.LessThan(1.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set( model, @@ -563,10 +291,10 @@ end MOI.optimize!(model) @test MOI.get(model, MOI.ConstraintDual(), xl) == 1.0 @test MOI.get(model, MOI.ConstraintDual(), xu) == 0.0 + return end -# TODO move to MOI -@testset "PR #121" begin +function test_pr_121() model = GLPK.Optimizer() ci = MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, @@ -581,9 +309,10 @@ end ci, ) @test_throws MOI.InvalidIndex(ci) MOI.delete(model, ci) + return end -@testset "Non-ascii names" begin +function test_nonascii_names() model = GLPK.Optimizer() x = MOI.add_variable(model) MOI.set(model, MOI.VariableName(), x, "ω") @@ -595,15 +324,18 @@ end ) MOI.set(model, MOI.ConstraintName(), c, "ω") @test MOI.get(model, MOI.ConstraintName(), c) == "ω" + return end -@testset "Deprecated constants" begin +function test_deprecated_constants() model = GLPK.Optimizer() - MOI.set(model, MOI.RawParameter("msg_lev"), GLPK.MSG_OFF) - @test MOI.get(model, MOI.RawParameter("msg_lev")) == GLPK.GLP_MSG_OFF + MOI.set(model, MOI.RawOptimizerAttribute("msg_lev"), GLPK.MSG_OFF) + @test MOI.get(model, MOI.RawOptimizerAttribute("msg_lev")) == + GLPK.GLP_MSG_OFF + return end -@testset "MOI.copy" begin +function test_copy_to() dest = GLPK.Optimizer() src = MOI.Utilities.Model{Float64}() MOI.Utilities.loadfromstring!( @@ -611,23 +343,23 @@ end """ variables: a, b, c, d minobjective: a + b + c + d - c1: a >= 1.0 - c2: b <= 2.0 - c3: c == 3.0 - c4: d in Interval(-4.0, 4.0) - c5: a in Integer() - c6: b in ZeroOne() + a >= 1.0 + b <= 2.0 + c == 3.0 + d in Interval(-4.0, 4.0) + a in Integer() + b in ZeroOne() c7: a + b >= -1.1 c8: a + b <= 2.2 c8: c + d == 2.2 """, ) - index_map = MOI.copy_to(dest, src; copy_names = true) + index_map = MOI.copy_to(dest, src) @test length(index_map) == 13 for (k, v) in index_map if v isa MOI.VariableIndex @test k == v - elseif v isa MOI.ConstraintIndex{MOI.SingleVariable} + elseif v isa MOI.ConstraintIndex{MOI.VariableIndex} @test k == v else # The order of the linear constraints may change. But they should be @@ -640,152 +372,13 @@ end @test length(v) == 4 names = MOI.get.(dest, MOI.VariableName(), v) @test names == ["a", "b", "c", "d"] + return end -@testset "dual_farkas" begin - @testset "test_farkas_dual_min" begin - model = GLPK.Optimizer() - 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.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 - - @testset "test_farkas_dual_max" begin - model = GLPK.Optimizer() - 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.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 - - @testset "test_farkas_dual_min_ii" begin - model = GLPK.Optimizer() - 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 - - @testset "test_farkas_dual_max_ii" begin - model = GLPK.Optimizer() - 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 - end -end - -@testset "want_infeasibility_certificates" begin +function test_want_infeasibility_certificates() model = GLPK.Optimizer(want_infeasibility_certificates = false) x = MOI.add_variables(model, 2) - MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.LessThan(0.0)) + MOI.add_constraint.(model, x, MOI.LessThan(0.0)) MOI.add_constraint( model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), @@ -794,21 +387,24 @@ end MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION + return end -@testset "large_time_limits" begin +function test_large_time_limits() model = GLPK.Optimizer() MOI.set(model, MOI.TimeLimitSec(), 1e9) @test MOI.get(model, MOI.TimeLimitSec()) == typemax(Cint) / 1_000 + return end -@testset "fractional_time_limits" begin +function test_fractional_time_limits() model = GLPK.Optimizer() MOI.set(model, MOI.TimeLimitSec(), 1.2345) @test MOI.get(model, MOI.TimeLimitSec()) == 1.235 + return end -@testset "empty problem" begin +function test_empty_problem() model = MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), GLPK.Optimizer(), @@ -817,9 +413,10 @@ end MOI.add_variable(model) MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + return end -@testset "empty_problem_infeasible" begin +function test_empty_problem_infeasible() model = GLPK.Optimizer() x = MOI.add_variable(model) MOI.add_constraint( @@ -836,9 +433,10 @@ end @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE @test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION + return end -@testset "empty_problem_unbounded" begin +function test_empty_problem_unbounded() model = GLPK.Optimizer() x = MOI.add_variable(model) MOI.add_constraint( @@ -853,4 +451,9 @@ end @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE @test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION + return end + +end # module + +TestMOIWrapper.runtests()