diff --git a/Project.toml b/Project.toml index f6748e4d..4c6acdf4 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Xpress" uuid = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054" authors = ["Joaquim Dias Garcia , Dheepak Krishnamurthy , Jose Daniel Lara "] url = "https://github.com/jump-dev/Xpress.jl" -version = "0.13.2" +version = "0.14.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -11,7 +11,7 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -MathOptInterface = "~0.9.20" +MathOptInterface = "0.10.5" julia = "1" [extras] diff --git a/src/MOI/MOI_wrapper.jl b/src/MOI/MOI_wrapper.jl index 67a55b3b..38018bd3 100644 --- a/src/MOI/MOI_wrapper.jl +++ b/src/MOI/MOI_wrapper.jl @@ -68,12 +68,12 @@ const SIMPLE_SCALAR_SETS = Union{ } const INDICATOR_SETS = Union{ - MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE,MOI.GreaterThan{Float64}}, - MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO,MOI.GreaterThan{Float64}}, - MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE,MOI.LessThan{Float64}}, - MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO,MOI.LessThan{Float64}}, - MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE,MOI.EqualTo{Float64}}, - MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO,MOI.EqualTo{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.GreaterThan{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ZERO,MOI.GreaterThan{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ZERO,MOI.LessThan{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.EqualTo{Float64}}, + MOI.Indicator{MOI.ACTIVATE_ON_ZERO,MOI.EqualTo{Float64}}, } mutable struct VariableInfo @@ -86,7 +86,7 @@ mutable struct VariableInfo # Storage for constraint names associated with variables because Xpress 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., + # because, at most, there can be three VariableIndex constraints, e.g., # LessThan, GreaterThan, and Integer. lessthan_name::String greaterthan_interval_or_equalto_name::String @@ -194,14 +194,13 @@ mutable struct Optimizer <: MOI.AbstractOptimizer # An enum to remember what objective is currently stored in the model. objective_type::ObjectiveType - # A flag to keep track of MOI.FEASIBILITY_SENSE, since Xpress only stores - # MIN_SENSE or MAX_SENSE. This allows us to differentiate between MIN_SENSE - # and FEASIBILITY_SENSE. - is_feasibility::Bool + # track whether objective function is set and the state of objective sense + is_objective_set::Bool + objective_sense::Union{Nothing,MOI.OptimizationSense} # A mapping from the MOI.VariableIndex to the Xpress column. VariableInfo # also stores some additional fields like what bounds have been added, the - # variable type, and the names of SingleVariable-in-Set constraints. + # variable type, and the names of VariableIndex-in-Set constraints. variable_info::CleverDicts.CleverDict{MOI.VariableIndex, VariableInfo} # An index that is incremented for each new constraint (regardless of type). @@ -211,7 +210,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer last_constraint_index::Int # ScalarAffineFunction{Float64}-in-Set storage. # ScalarQuadraticFunction{Float64}-in-Set storage. - # VectorAffineFunction{Float64}-in-IndicatorSet storage. + # VectorAffineFunction{Float64}-in-Indicator storage. # VectorOfVariables-in-(R)SOC) storage. affine_constraint_info::Dict{Int, ConstraintInfo} # VectorOfVariables-in-Set storage. @@ -274,7 +273,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer model.message_callback = nothing for (name, value) in kwargs - name = MOI.RawParameter(string(name)) + name = MOI.RawOptimizerAttribute(string(name)) model.params[name] = value end @@ -298,22 +297,23 @@ function MOI.empty!(model::Optimizer) MOI.set(model, name, value) end - MOI.set(model, MOI.RawParameter("MPSNAMELENGTH"), 64) - MOI.set(model, MOI.RawParameter("CALLBACKFROMMASTERTHREAD"), 1) + MOI.set(model, MOI.RawOptimizerAttribute("MPSNAMELENGTH"), 64) + MOI.set(model, MOI.RawOptimizerAttribute("CALLBACKFROMMASTERTHREAD"), 1) - MOI.set(model, MOI.RawParameter("XPRESS_WARNING_WINDOWS"), model.show_warning) + MOI.set(model, MOI.RawOptimizerAttribute("XPRESS_WARNING_WINDOWS"), model.show_warning) # disable log caching previous state log_level = model.log_level - log_level != 0 && MOI.set(model, MOI.RawParameter("OUTPUTLOG"), 0) + log_level != 0 && MOI.set(model, MOI.RawOptimizerAttribute("OUTPUTLOG"), 0) # silently load a empty model - to avoid useless printing Xpress.loadlp(model.inner) # re-enable logging - log_level != 0 && MOI.set(model, MOI.RawParameter("OUTPUTLOG"), log_level) + log_level != 0 && MOI.set(model, MOI.RawOptimizerAttribute("OUTPUTLOG"), log_level) model.name = "" model.objective_type = SCALAR_AFFINE - model.is_feasibility = true + model.is_objective_set = false + model.objective_sense = nothing empty!(model.variable_info) model.last_constraint_index = 0 empty!(model.affine_constraint_info) @@ -350,7 +350,8 @@ end function MOI.is_empty(model::Optimizer) !isempty(model.name) && return false model.objective_type != SCALAR_AFFINE && return false - model.is_feasibility == false && return false + model.is_objective_set == true && return false + model.objective_sense !== nothing && return false !isempty(model.variable_info) && return false length(model.affine_constraint_info) != 0 && return false length(model.sos_constraint_info) != 0 && return false @@ -432,11 +433,19 @@ end MOI.get(::Optimizer, ::MOI.SolverName) = "Xpress" +# Currently this returns the version of the Xpress package as a whole +# which is different from the Xpress Optimizer version +# the first is a good match because is the version number that appears +# in the dowload package +function MOI.get(optimizer::Optimizer, ::MOI.SolverVersion) + MOI.get(optimizer, MOI.RawOptimizerAttribute("XPRESSVERSION")) +end + function MOI.supports( ::Optimizer, ::MOI.ObjectiveFunction{F} ) where {F <: Union{ - MOI.SingleVariable, + MOI.VariableIndex, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}, }} @@ -444,7 +453,7 @@ function MOI.supports( end function MOI.supports_constraint( - ::Optimizer, ::Type{MOI.SingleVariable}, ::Type{F} + ::Optimizer, ::Type{MOI.VariableIndex}, ::Type{F} ) where {F <: Union{ MOI.EqualTo{Float64}, MOI.LessThan{Float64}, @@ -515,14 +524,14 @@ MOI.supports(::Optimizer, ::MOI.Silent) = true MOI.supports(::Optimizer, ::MOI.NumberOfThreads) = 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 -function MOI.set(model::Optimizer, param::MOI.RawParameter, value) +function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value) # Always store value in params dictionary when setting # This is because when calling `empty!` we create a new XpressProblem and # and want to set all the raw parameters and attributes again. model.params[param] = value - if param == MOI.RawParameter("logfile") + if param == MOI.RawOptimizerAttribute("logfile") if value == "" # disable log file Xpress.setlogfile(model.inner, C_NULL) @@ -531,17 +540,17 @@ function MOI.set(model::Optimizer, param::MOI.RawParameter, value) end model.inner.logfile = value reset_message_callback(model) - elseif param == MOI.RawParameter("MOI_IGNORE_START") + elseif param == MOI.RawOptimizerAttribute("MOI_IGNORE_START") model.ignore_start = value - elseif param == MOI.RawParameter("MOI_WARNINGS") + elseif param == MOI.RawOptimizerAttribute("MOI_WARNINGS") model.moi_warnings = value - elseif param == MOI.RawParameter("MOI_SOLVE_MODE") + elseif param == MOI.RawOptimizerAttribute("MOI_SOLVE_MODE") # https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/R/HTML/lpoptimize.html model.solve_method = value - elseif param == MOI.RawParameter("XPRESS_WARNING_WINDOWS") + elseif param == MOI.RawOptimizerAttribute("XPRESS_WARNING_WINDOWS") model.show_warning = value reset_message_callback(model) - elseif param == MOI.RawParameter("OUTPUTLOG") + elseif param == MOI.RawOptimizerAttribute("OUTPUTLOG") model.log_level = value Xpress.setcontrol!(model.inner, "OUTPUTLOG", value) reset_message_callback(model) @@ -564,16 +573,16 @@ function reset_message_callback(model) end end -function MOI.get(model::Optimizer, param::MOI.RawParameter) - if param == MOI.RawParameter("logfile") +function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute) + if param == MOI.RawOptimizerAttribute("logfile") return model.inner.logfile - elseif param == MOI.RawParameter("MOI_IGNORE_START") + elseif param == MOI.RawOptimizerAttribute("MOI_IGNORE_START") return model.ignore_start - elseif param == MOI.RawParameter("MOI_WARNINGS") + elseif param == MOI.RawOptimizerAttribute("MOI_WARNINGS") return model.moi_warnings - elseif param == MOI.RawParameter("MOI_SOLVE_MODE") + elseif param == MOI.RawOptimizerAttribute("MOI_SOLVE_MODE") return model.solve_method - elseif param == MOI.RawParameter("XPRESS_WARNING_WINDOWS") + elseif param == MOI.RawOptimizerAttribute("XPRESS_WARNING_WINDOWS") return model.show_warning else return Xpress.get_control_or_attribute(model.inner, param.name) @@ -583,26 +592,42 @@ end function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Real) # positive values would mean that its stops after `limit` seconds # iff there is already a MIP solution available. - MOI.set(model, MOI.RawParameter("MAXTIME"), -floor(Int32, limit)) + MOI.set(model, MOI.RawOptimizerAttribute("MAXTIME"), -floor(Int32, limit)) return end function MOI.get(model::Optimizer, ::MOI.TimeLimitSec) - return -MOI.get(model, MOI.RawParameter("MAXTIME")) + # MOI.attribute_value_type(MOI.TimeLimitSec()) = Union{Nothing, Float64} + return convert(Float64, -MOI.get(model, MOI.RawOptimizerAttribute("MAXTIME"))) end -MOI.Utilities.supports_default_copy_to(::Optimizer, ::Bool) = true +MOI.supports_incremental_interface(::Optimizer) = true -function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kwargs...) - return MOI.Utilities.automatic_copy_to(dest, src; kwargs...) +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) + return MOI.Utilities.default_copy_to(dest, src) end function MOI.get(model::Optimizer, ::MOI.ListOfVariableAttributesSet) - return MOI.AbstractVariableAttribute[MOI.VariableName()] + ret = MOI.AbstractVariableAttribute[] + found_name = any(!isempty(info.name) for info in values(model.variable_info)) + found_start = any(info.start !== nothing for info in values(model.variable_info)) + if found_name + push!(ret, MOI.VariableName()) + end + if found_start + push!(ret, MOI.VariablePrimalStart()) + end + return ret end function MOI.get(model::Optimizer, ::MOI.ListOfModelAttributesSet) - attributes = Any[MOI.ObjectiveSense()] + if MOI.is_empty(model) + return Any[] + end + attributes = Any[] + if model.objective_sense !== nothing + push!(attributes, MOI.ObjectiveSense()) + end typ = MOI.get(model, MOI.ObjectiveFunctionType()) if typ !== nothing push!(attributes, MOI.ObjectiveFunction{typ}()) @@ -613,8 +638,14 @@ function MOI.get(model::Optimizer, ::MOI.ListOfModelAttributesSet) return attributes end -function MOI.get(model::Optimizer, ::MOI.ListOfConstraintAttributesSet) - return MOI.AbstractConstraintAttribute[MOI.ConstraintName()] +function MOI.get(model::Optimizer, ::MOI.ListOfConstraintAttributesSet{F, S}) where {S, F} + ret = MOI.AbstractConstraintAttribute[] + constraint_indices = MOI.get(model, MOI.ListOfConstraintIndices{F, S}()) + found_name = any(!isempty(MOI.get(model, MOI.ConstraintName(), index)) for index in constraint_indices) + if found_name + push!(ret, MOI.ConstraintName()) + end + return ret end function _indices_and_coefficients( @@ -624,7 +655,7 @@ function _indices_and_coefficients( f::MOI.ScalarAffineFunction{Float64} ) for (i, term) in enumerate(f.terms) - indices[i] = _info(model, term.variable_index).column + indices[i] = _info(model, term.variable).column coefficients[i] = term.coefficient end return indices, coefficients @@ -650,7 +681,7 @@ function _indices_and_coefficients_indicator( i = 1 for fi in f.terms if fi.output_index != 1 - indices[i] = _info(model,fi.scalar_term.variable_index).column + indices[i] = _info(model,fi.scalar_term.variable).column coefficients[i] = fi.scalar_term.coefficient i += 1 end @@ -668,8 +699,8 @@ function _indices_and_coefficients( f::MOI.ScalarQuadraticFunction ) for (i, term) in enumerate(f.quadratic_terms) - I[i] = _info(model, term.variable_index_1).column - J[i] = _info(model, term.variable_index_2).column + I[i] = _info(model, term.variable_1).column + J[i] = _info(model, term.variable_2).column V[i] = term.coefficient # MOI represents objective as 0.5 x' Q x @@ -696,7 +727,7 @@ function _indices_and_coefficients( end for (i, term) in enumerate(f.affine_terms) - indices[i] = _info(model, term.variable_index).column + indices[i] = _info(model, term.variable).column coefficients[i] = term.coefficient end return @@ -797,7 +828,7 @@ function MOI.delete(model::Optimizer, v::MOI.VariableIndex) end end model.name_to_variable = nothing - # We throw away name_to_constraint_index so we will rebuild SingleVariable + # We throw away name_to_constraint_index so we will rebuild VariableIndex # constraint names without v. model.name_to_constraint_index = nothing return @@ -1023,49 +1054,44 @@ function MOI.set( # TODO: should this propagate across a `MOI.empty!(optimizer)` call if sense == MOI.MIN_SENSE Xpress.chgobjsense(model.inner, :Min) - model.is_feasibility = false elseif sense == MOI.MAX_SENSE Xpress.chgobjsense(model.inner, :Max) - model.is_feasibility = false else @assert sense == MOI.FEASIBILITY_SENSE _zero_objective(model) Xpress.chgobjsense(model.inner, :Min) - model.is_feasibility = true end + model.objective_sense = sense return end function MOI.get(model::Optimizer, ::MOI.ObjectiveSense) - sense = Xpress.objective_sense(model.inner) - if model.is_feasibility - return MOI.FEASIBILITY_SENSE - elseif sense == :maximize - return MOI.MAX_SENSE + if model.objective_sense !== nothing + return model.objective_sense else - @assert sense == :minimize - return MOI.MIN_SENSE + return MOI.FEASIBILITY_SENSE end end function MOI.set( model::Optimizer, ::MOI.ObjectiveFunction{F}, f::F -) where {F <: MOI.SingleVariable} +) where {F <: MOI.VariableIndex} MOI.set( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), convert(MOI.ScalarAffineFunction{Float64}, f) ) model.objective_type = SINGLE_VARIABLE + model.is_objective_set = true return end -function MOI.get(model::Optimizer, ::MOI.ObjectiveFunction{MOI.SingleVariable}) +function MOI.get(model::Optimizer, ::MOI.ObjectiveFunction{MOI.VariableIndex}) obj = MOI.get( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}() ) - return convert(MOI.SingleVariable, obj) + return convert(MOI.VariableIndex, obj) end function MOI.set( @@ -1080,12 +1106,13 @@ function MOI.set( # are removed obj = zeros(Float64, num_vars) for term in f.terms - column = _info(model, term.variable_index).column + column = _info(model, term.variable).column obj[column] += term.coefficient end Xpress.chgobj(model.inner, collect(1:num_vars), obj) Xpress.chgobj(model.inner, [0], [-f.constant]) model.objective_type = SCALAR_AFFINE + model.is_objective_set = true return end @@ -1121,6 +1148,7 @@ function MOI.set( end Xpress.chgmqobj(model.inner, I, J, V) model.objective_type = SCALAR_QUADRATIC + model.is_objective_set = true return end @@ -1164,7 +1192,7 @@ function MOI.get( end affine_terms = MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()).terms constant = Xpress.getdblattrib(model.inner, Xpress.Lib.XPRS_OBJRHS) - return MOI.ScalarQuadraticFunction(affine_terms, q_terms, constant) + return MOI.ScalarQuadraticFunction(q_terms, affine_terms, constant) end function MOI.modify( @@ -1177,11 +1205,11 @@ function MOI.modify( end ## -## SingleVariable-in-Set constraints. +## VariableIndex-in-Set constraints. ## function _info( - model::Optimizer, c::MOI.ConstraintIndex{MOI.SingleVariable, <:Any} + model::Optimizer, c::MOI.ConstraintIndex{MOI.VariableIndex, <:Any} ) var_index = MOI.VariableIndex(c.value) if haskey(model.variable_info, var_index) @@ -1192,7 +1220,7 @@ 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) @@ -1203,7 +1231,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) @@ -1214,7 +1242,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 @@ -1222,7 +1250,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 @@ -1230,7 +1258,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 @@ -1238,7 +1266,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 @@ -1246,7 +1274,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semicontinuous{Float64}} ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).type == SEMICONTINUOUS @@ -1254,7 +1282,7 @@ end function MOI.is_valid( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semiinteger{Float64}} ) return haskey(model.variable_info, MOI.VariableIndex(c.value)) && _info(model, c).type == SEMIINTEGER @@ -1262,17 +1290,17 @@ 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, ::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) @@ -1328,9 +1356,9 @@ function _throw_if_existing_upper( end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, s::S + model::Optimizer, f::MOI.VariableIndex, s::S ) where {S <: SCALAR_SETS} - info = _info(model, f.variable) + info = _info(model, f) if info.type == BINARY lower, upper = _bounds(s) if lower !== nothing @@ -1350,83 +1378,61 @@ function MOI.add_constraint( end end 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 function MOI.add_constraints( - model::Optimizer, f::Vector{MOI.SingleVariable}, s::Vector{S} + model::Optimizer, f::Vector{MOI.VariableIndex}, s::Vector{S} ) where {S <: SCALAR_SETS} for fi in f - info = _info(model, fi.variable) + info = _info(model, fi) if S <: MOI.LessThan{Float64} - _throw_if_existing_upper(info.bound, info.type, S, fi.variable) + _throw_if_existing_upper(info.bound, info.type, S, fi) 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, fi.variable) + _throw_if_existing_lower(info.bound, info.type, S, fi) 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, fi.variable) - _throw_if_existing_upper(info.bound, info.type, S, fi.variable) + _throw_if_existing_lower(info.bound, info.type, S, fi) + _throw_if_existing_upper(info.bound, info.type, S, fi) info.bound = EQUAL_TO else @assert S <: MOI.Interval{Float64} - _throw_if_existing_lower(info.bound, info.type, S, fi.variable) - _throw_if_existing_upper(info.bound, info.type, S, fi.variable) + _throw_if_existing_lower(info.bound, info.type, S, fi) + _throw_if_existing_upper(info.bound, info.type, S, fi) info.bound = INTERVAL end end indices = [ - MOI.ConstraintIndex{MOI.SingleVariable, eltype(s)}(fi.variable.value) + MOI.ConstraintIndex{MOI.VariableIndex, eltype(s)}(fi.value) for fi in f ] - _set_bounds(model, indices, s) - return indices -end - -function _set_bounds( - model::Optimizer, - indices::Vector{MOI.ConstraintIndex{MOI.SingleVariable, S}}, - sets::Vector{S} -) where {S} - columns, senses, values = Int[], Cchar[], Float64[] - for (c, s) in zip(indices, sets) - lower, upper = _bounds(s) - info = _info(model, c) - if lower !== nothing - push!(columns, info.column) - push!(senses, Cchar('L')) - push!(values, lower) - end - if upper !== nothing - push!(columns, info.column) - push!(senses, Cchar('U')) - push!(values, upper) - end + for (index, s) in zip(indices, s) + MOI.set(model, MOI.ConstraintSet(), index, s) end - Xpress.chgbounds(model.inner, columns, senses, values) - return + return indices 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) @@ -1534,7 +1540,7 @@ 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) @@ -1556,7 +1562,7 @@ 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) @@ -1575,7 +1581,7 @@ 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) @@ -1595,7 +1601,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 = _get_variable_lower_bound(model, _info(model, c)) @@ -1605,7 +1611,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 = _get_variable_upper_bound(model, _info(model, c)) @@ -1614,7 +1620,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 = _get_variable_lower_bound(model, _info(model, c)) @@ -1623,7 +1629,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) info = _info(model, c) @@ -1635,7 +1641,7 @@ end function MOI.set( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, S}, s::S + c::MOI.ConstraintIndex{MOI.VariableIndex, S}, s::S ) where {S<:SCALAR_SETS} MOI.throw_if_not_valid(model, c) lower, upper = _bounds(s) @@ -1652,9 +1658,9 @@ function MOI.set( end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, ::MOI.ZeroOne + model::Optimizer, f::MOI.VariableIndex, ::MOI.ZeroOne ) - info = _info(model, f.variable) + info = _info(model, f) info.previous_lower_bound = _get_variable_lower_bound(model, info) info.previous_upper_bound = _get_variable_upper_bound(model, info) lower, upper = info.previous_lower_bound, info.previous_upper_bound @@ -1677,11 +1683,11 @@ function MOI.add_constraint( end Xpress.chgcoltype(model.inner, [info.column], Cchar['B']) info.type = BINARY - 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} + model::Optimizer, c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.ZeroOne} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1697,23 +1703,23 @@ 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() end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, ::MOI.Integer + model::Optimizer, f::MOI.VariableIndex, ::MOI.Integer ) - info = _info(model, f.variable) + info = _info(model, f) Xpress.chgcoltype(model.inner, [info.column], Cchar['I']) info.type = INTEGER - 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} + model::Optimizer, c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Integer} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1727,28 +1733,28 @@ 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.add_constraint( - model::Optimizer, f::MOI.SingleVariable, s::MOI.Semicontinuous{Float64} + model::Optimizer, f::MOI.VariableIndex, s::MOI.Semicontinuous{Float64} ) - info = _info(model, f.variable) - _throw_if_existing_lower(info.bound, info.type, typeof(s), f.variable) - _throw_if_existing_upper(info.bound, info.type, typeof(s), f.variable) + info = _info(model, f) + _throw_if_existing_lower(info.bound, info.type, typeof(s), f) + _throw_if_existing_upper(info.bound, info.type, typeof(s), f) Xpress.chgcoltype(model.inner, [info.column], Cchar['S']) _set_variable_semi_lower_bound(model, info, s.lower) _set_variable_upper_bound(model, info, s.upper) info.type = SEMICONTINUOUS - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{Float64}}(f.variable.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semicontinuous{Float64}}(f.value) end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semicontinuous{Float64}} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1765,7 +1771,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semicontinuous{Float64}} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1775,21 +1781,21 @@ function MOI.get( end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, s::MOI.Semiinteger{Float64} + model::Optimizer, f::MOI.VariableIndex, s::MOI.Semiinteger{Float64} ) - info = _info(model, f.variable) - _throw_if_existing_lower(info.bound, info.type, typeof(s), f.variable) - _throw_if_existing_upper(info.bound, info.type, typeof(s), f.variable) + info = _info(model, f) + _throw_if_existing_lower(info.bound, info.type, typeof(s), f) + _throw_if_existing_upper(info.bound, info.type, typeof(s), f) Xpress.chgcoltype(model.inner, [info.column], Cchar['R']) _set_variable_semi_lower_bound(model, info, s.lower) _set_variable_upper_bound(model, info, s.upper) info.type = SEMIINTEGER - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{Float64}}(f.variable.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semiinteger{Float64}}(f.value) end function MOI.delete( model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semiinteger{Float64}} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1806,7 +1812,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Semiinteger{Float64}} ) MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1818,7 +1824,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where {S} MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -1835,7 +1841,7 @@ end function MOI.set( model::Optimizer, ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable, S}, name::String + c::MOI.ConstraintIndex{MOI.VariableIndex, S}, name::String ) where {S} MOI.throw_if_not_valid(model, c) info = _info(model, c) @@ -2184,7 +2190,7 @@ function _rebuild_name_to_constraint_index_variables(model::Optimizer) model.name_to_constraint_index[constraint_name] = nothing else model.name_to_constraint_index[constraint_name] = - MOI.ConstraintIndex{MOI.SingleVariable, S}(key.value) + MOI.ConstraintIndex{MOI.VariableIndex, S}(key.value) end end end @@ -2198,7 +2204,7 @@ end function MOI.get( model::Optimizer, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.VectorAffineFunction{Float64}, MOI.IndicatorSet{A,S}} + c::MOI.ConstraintIndex{MOI.VectorAffineFunction{Float64}, MOI.Indicator{A,S}} ) where {A, S <: SCALAR_SETS} # rhs = Vector{Cdouble}(undef, 1) # info = _info(model, c) @@ -2206,7 +2212,7 @@ function MOI.get( # set_cte = MOI.constant(info.set.set) # # a^T x + b <= c ===> a^T <= c - b # Xpress.getrhs!(model.inner, rhs, row, row) - # return MOI.IndicatorSet{A}(S(rhs[1])) + # return MOI.Indicator{A}(S(rhs[1])) return _info(model, c).set end @@ -2215,7 +2221,7 @@ function _indicator_variable(f::MOI.VectorAffineFunction) changes = 0 for fi in f.terms if fi.output_index == 1 - value = fi.scalar_term.variable_index.value + value = fi.scalar_term.variable.value changes += 1 end end @@ -2229,7 +2235,7 @@ indicator_activation(::Type{Val{MOI.ACTIVATE_ON_ZERO}}) = Cint(-1) indicator_activation(::Type{Val{MOI.ACTIVATE_ON_ONE}}) = Cint(1) function MOI.add_constraint( - model::Optimizer, f::MOI.VectorAffineFunction{T}, is::MOI.IndicatorSet{A, LT}) where {T<:Real, LT<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},A} + model::Optimizer, f::MOI.VectorAffineFunction{T}, is::MOI.Indicator{A, LT}) where {T<:Real, LT<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},A} con_value = _indicator_variable(f) model.last_constraint_index += 1 model.affine_constraint_info[model.last_constraint_index] = @@ -2327,7 +2333,7 @@ function MOI.get( ) ) end - return MOI.ScalarQuadraticFunction(affine_terms, quadratic_terms, 0.0) + return MOI.ScalarQuadraticFunction(quadratic_terms, affine_terms, 0.0) end ### @@ -2759,7 +2765,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 term_stat = MOI.get(model, MOI.TerminationStatus()) @@ -2801,7 +2807,7 @@ end function MOI.get(model::Optimizer, attr::MOI.DualStatus) _throw_if_optimize_in_progress(model, attr) - if attr.N != 1 + if attr.result_index != 1 return MOI.NO_SOLUTION elseif is_mip(model) return MOI.NO_SOLUTION @@ -2826,7 +2832,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) @@ -2884,7 +2890,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) @@ -2915,7 +2921,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) @@ -2946,7 +2952,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.EqualTo{Float64}} ) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -2960,7 +2966,7 @@ end function MOI.get( model::Optimizer, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{Float64}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{Float64}} ) _throw_if_optimize_in_progress(model, attr) MOI.check_result_index_bounds(model, attr) @@ -3014,7 +3020,7 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveBound) end 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.cached_solution.solve_time end @@ -3066,16 +3072,16 @@ function MOI.get(model::Optimizer, ::MOI.Silent) end function MOI.set(model::Optimizer, ::MOI.Silent, flag::Bool) - MOI.set(model, MOI.RawParameter("OUTPUTLOG"), ifelse(flag, 0, 1)) + MOI.set(model, MOI.RawOptimizerAttribute("OUTPUTLOG"), ifelse(flag, 0, 1)) return end function MOI.get(model::Optimizer, ::MOI.NumberOfThreads) - return Int(MOI.get(model, MOI.RawParameter("THREADS"))) + return Int(MOI.get(model, MOI.RawOptimizerAttribute("THREADS"))) end function MOI.set(model::Optimizer, ::MOI.NumberOfThreads, x::Int) - return MOI.set(model, MOI.RawParameter("THREADS"), x) + return MOI.set(model, MOI.RawOptimizerAttribute("THREADS"), x) end function MOI.get(model::Optimizer, ::MOI.Name) @@ -3135,12 +3141,12 @@ _type_enums(::Type{<:MOI.Semiinteger}) = (SEMIINTEGER,) _type_enums(::Any) = (nothing,) function MOI.get( - model::Optimizer, ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S} + model::Optimizer, ::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) @@ -3176,31 +3182,31 @@ 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)) elseif info.type == SEMICONTINUOUS - push!(constraints, (MOI.SingleVariable, MOI.Semicontinuous{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.Semicontinuous{Float64})) elseif info.type == SEMIINTEGER - push!(constraints, (MOI.SingleVariable, MOI.Semiinteger{Float64})) + push!(constraints, (MOI.VariableIndex, MOI.Semiinteger{Float64})) end end for info in values(model.affine_constraint_info) @@ -3225,10 +3231,10 @@ function MOI.get(model::Optimizer, ::MOI.ListOfConstraints) end function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType) - if model.is_feasibility + if !model.is_objective_set return nothing elseif model.objective_type == SINGLE_VARIABLE - return MOI.SingleVariable + return MOI.VariableIndex elseif model.objective_type == SCALAR_AFFINE return MOI.ScalarAffineFunction{Float64} else @@ -3256,8 +3262,10 @@ function MOI.modify( c::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, chg::MOI.ScalarCoefficientChange{Float64} ) + @assert model.objective_type == SCALAR_AFFINE column = _info(model, chg.variable).column Xpress.chgobj(model.inner, [column], [chg.new_coefficient]) + model.is_objective_set = true return end @@ -3283,7 +3291,7 @@ function _replace_with_matching_sparsity!( replacement::MOI.ScalarAffineFunction, row::Int ) for term in replacement.terms - col = _info(model, term.variable_index).column + col = _info(model, term.variable).column Xpress.chgcoef( model.inner, row, col, MOI.coefficient(term) ) @@ -3316,13 +3324,13 @@ function _replace_with_different_sparsity!( ) # First, zero out the old constraint function terms. for term in previous.terms - col = _info(model, term.variable_index).column + col = _info(model, term.variable).column Xpress.chgcoef(model.inner, row, col, 0.0) end # Next, set the new constraint function terms. for term in previous.terms - col = _info(model, term.variable_index).column + col = _info(model, term.variable).column Xpress.chgcoef(model.inner, row, col, MOI.coefficient(term)) end return @@ -3418,10 +3426,10 @@ function MOI.get( end function MOI.get( - model::Optimizer, ::MOI.ConstraintBasisStatus, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} -) where {S <: SCALAR_SETS} - column = _info(model, c).column + model::Optimizer, ::MOI.VariableBasisStatus, + x::MOI.VariableIndex +) + column = _info(model, x).column basis_status = model.basis_status if basis_status == nothing _generate_basis_status(model::Optimizer) @@ -3432,21 +3440,9 @@ function MOI.get( if vbasis == 1 return MOI.BASIC elseif vbasis == 0 - 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 == 2 - 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 == 3 return MOI.SUPER_BASIC else @@ -3762,7 +3758,7 @@ function getinfeasbounds(model::Optimizer) if model.moi_warnings @warn "Xpress can't find IIS with invalid bounds, the constraints that keep the model infeasible can't be found, only the infeasible bounds will be available" end - col = 1 + col = 0 ncols = 0 infeas_cols = [] for check_col in check_bounds @@ -3787,7 +3783,7 @@ function getfirstiis(model::Optimizer) if status_code[1] == 1 # The problem is actually feasible. return IISData(status_code[1], true, 0, 0, Vector{Cint}(undef, 0), Vector{Cint}(undef, 0), Vector{UInt8}(undef, 0), Vector{UInt8}(undef, 0)) - elseif status_code[1] == 2 + elseif 2 <= status_code[1] <= 3 # 2 = error, 3 = timeout ncols, miiscol = getinfeasbounds(model) return IISData(status_code[1], false, 0, ncols, Vector{Cint}(undef, 0), miiscol, Vector{UInt8}(undef, 0), Vector{UInt8}(undef, 0)) end @@ -3799,7 +3795,8 @@ function getfirstiis(model::Optimizer) num = Cint(1) rownumber = Vector{Cint}(undef, 1) colnumber = Vector{Cint}(undef, 1) - Xpress.getiisdata(model.inner, num, rownumber, colnumber, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL) + Xpress.getiisdata( + model.inner, num, rownumber, colnumber, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL, C_NULL) nrows = rownumber[1] ncols = colnumber[1] @@ -3807,10 +3804,10 @@ function getfirstiis(model::Optimizer) miiscol = Vector{Cint}(undef, ncols) constrainttype = Vector{UInt8}(undef, nrows) colbndtype = Vector{UInt8}(undef, ncols) - Xpress.getiisdata(model.inner, num, rownumber, colnumber, miisrow, miiscol, constrainttype, colbndtype, C_NULL, C_NULL, C_NULL, C_NULL) + Xpress.getiisdata( + model.inner, num, rownumber, colnumber, miisrow, miiscol, constrainttype, colbndtype, C_NULL, C_NULL, C_NULL, C_NULL) return IISData(status_code[1], true, nrows, ncols, miisrow, miiscol, constrainttype, colbndtype) - end function MOI.compute_conflict!(model::Optimizer) @@ -3832,9 +3829,12 @@ function MOI.get(model::Optimizer, ::MOI.ConflictStatus) # Currently this condition (!model.conflict.is_standard_iis) is always false. return MOI.CONFLICT_FOUND elseif model.conflict.stat == 1 - return MOI.NO_CONFLICT_FOUND + return MOI.NO_CONFLICT_EXISTS else - return error("IIS failed internally.") + # stat == 2 -> error + # stat == 3 -> timeout + return MOI.NO_CONFLICT_FOUND + # return error("IIS failed internally.") end end @@ -3847,15 +3847,74 @@ function MOI.get(model::Optimizer, ::MOI.ConstraintConflictStatus, index::MOI.Co return (_info(model, index).row - 1) in model.conflict.miisrow ? MOI.IN_CONFLICT : MOI.NOT_IN_CONFLICT end -function MOI.get(model::Optimizer, ::MOI.ConstraintConflictStatus, index::MOI.ConstraintIndex{<:MOI.SingleVariable, <:Union{MOI.LessThan, MOI.GreaterThan, MOI.EqualTo}}) +col_type_char(::Type{MOI.LessThan{Float64}}) = 'U' +col_type_char(::Type{MOI.GreaterThan{Float64}}) = 'L' +col_type_char(::Type{MOI.EqualTo{Float64}}) = 'F' +# col_type_char(::Type{MOI.Interval{Float64}}) = 'T' +col_type_char(::Type{MOI.ZeroOne}) = 'B' +col_type_char(::Type{MOI.Integer}) = 'I' +col_type_char(::Type{MOI.Semicontinuous{Float64}}) = 'S' +col_type_char(::Type{MOI.Semiinteger{Float64}}) = 'R' +function MOI.get( + model::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{MOI.VariableIndex, S} +) where S <: MOI.AbstractScalarSet _ensure_conflict_computed(model) - return (_info(model, index).column) in model.conflict.miiscol ? MOI.IN_CONFLICT : MOI.NOT_IN_CONFLICT + _char = col_type_char(S) + ref_col = _info(model, index).column - 1 + for (idx, col) in enumerate(model.conflict.miiscol) + if col == ref_col && ( + model.conflict.stat > 1 || + Char(model.conflict.colbndtype[idx]) == _char + ) + return MOI.IN_CONFLICT + end + end + return MOI.NOT_IN_CONFLICT +end +function MOI.get( + model::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{Float64}} +) + _ensure_conflict_computed(model) + ref_col = _info(model, index).column - 1 + for (idx, col) in enumerate(model.conflict.miiscol) + if col == ref_col && ( + model.conflict.stat > 1 || + Char(model.conflict.colbndtype[idx]) == 'U' || + Char(model.conflict.colbndtype[idx]) == 'L' + ) + return MOI.IN_CONFLICT + end + end + return MOI.NOT_IN_CONFLICT +end +function MOI.get( + model::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{MOI.VariableIndex, MOI.ZeroOne} +) where S <: MOI.AbstractScalarSet + _ensure_conflict_computed(model) + ref_col = _info(model, index).column - 1 + for (idx, col) in enumerate(model.conflict.miiscol) + if col == ref_col + if Char(model.conflict.colbndtype[idx]) == 'B' + return MOI.IN_CONFLICT + elseif Char(model.conflict.colbndtype[idx]) == 'U' + return MOI.MAYBE_IN_CONFLICT + elseif Char(model.conflict.colbndtype[idx]) == 'L' + return MOI.MAYBE_IN_CONFLICT + end + end + end + return MOI.NOT_IN_CONFLICT end - function MOI.supports( ::Optimizer, ::MOI.ConstraintConflictStatus, - ::Type{MOI.ConstraintIndex{<:MOI.SingleVariable, <:SCALAR_SETS}} + ::Type{MOI.ConstraintIndex{<:MOI.VariableIndex, <:SCALAR_SETS}} ) return true end diff --git a/test/.gitignore b/test/.gitignore index 397b4a76..65d58b88 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1 +1,2 @@ *.log +*.prt diff --git a/test/Derivative/DerivativeExample.jl b/test/Derivative/DerivativeExample.jl index 9513fdb0..2ce9124f 100644 --- a/test/Derivative/DerivativeExample.jl +++ b/test/Derivative/DerivativeExample.jl @@ -14,8 +14,8 @@ mutable struct DispachModel c_demand # Constraint of the generation by the demand end -#Simple Model of Thermal Generation Dispatch by Constraints with MOI.SingleVariable -function GenerateModel_SingleVariable() +#Simple Model of Thermal Generation Dispatch by Constraints with MOI.VariableIndex +function GenerateModel_VariableIndex() # Parameters d = 45.0 # Demand I = 3 # Number of generators @@ -33,10 +33,10 @@ function GenerateModel_SingleVariable() c_limit_inf = Vector{Any}(undef, I + 1) # Lower limit constraints c_limit_sup = Vector{Any}(undef, I) # Upper limit constraints for i in 1:I - c_limit_inf[i] = MOI.add_constraint(optimizer, MOI.SingleVariable(g[i]), MOI.GreaterThan(0.0)) - c_limit_sup[i] = MOI.add_constraint(optimizer, MOI.SingleVariable(g[i]), MOI.LessThan(g_sup[i])) + c_limit_inf[i] = MOI.add_constraint(optimizer, g[i], MOI.GreaterThan(0.0)) + c_limit_sup[i] = MOI.add_constraint(optimizer, g[i], MOI.LessThan(g_sup[i])) end - c_limit_inf[I+1] = MOI.add_constraint(optimizer, MOI.SingleVariable(Df), MOI.GreaterThan(0.0)) + c_limit_inf[I+1] = MOI.add_constraint(optimizer, Df, MOI.GreaterThan(0.0)) c_demand = MOI.add_constraint(optimizer, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0), @@ -158,8 +158,8 @@ end #Initialization of Parameters @testset "Results" begin @testset "Single_Variable" begin - #Generate model by SingleVariable - model1 = GenerateModel_SingleVariable() + #Generate model by VariableIndex + model1 = GenerateModel_VariableIndex() #Get the results of models with the DiffOpt Forward context @testset "Forward" begin resultForward = Forward(model1) diff --git a/test/MathOptInterface/MOI_callbacks.jl b/test/MathOptInterface/MOI_callbacks.jl index 86106d5e..ee345daf 100644 --- a/test/MathOptInterface/MOI_callbacks.jl +++ b/test/MathOptInterface/MOI_callbacks.jl @@ -4,10 +4,6 @@ using Random using Test const MOI = MathOptInterface -const MOIT = MathOptInterface.Test - -const MOIT = MOI.Test -const MOIU = MOI.Utilities function callback_simple_model() model = Xpress.Optimizer( @@ -43,7 +39,7 @@ function callback_knapsack_model() N = 30 x = MOI.add_variables(model, N) - MOI.add_constraints(model, MOI.SingleVariable.(x), MOI.ZeroOne()) + MOI.add_constraints(model, x, MOI.ZeroOne()) MOI.set.(model, MOI.VariablePrimalStart(), x, 0.0) Random.seed!(1) item_weights, item_values = rand(N), rand(N) diff --git a/test/MathOptInterface/MOI_Wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl similarity index 70% rename from test/MathOptInterface/MOI_Wrapper.jl rename to test/MathOptInterface/MOI_wrapper.jl index 17a835fb..80dbdb34 100644 --- a/test/MathOptInterface/MOI_Wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -1,195 +1,112 @@ +module TestMOIWrapper + using Xpress using MathOptInterface using Test const MOI = MathOptInterface -const MOIT = MathOptInterface.Test - -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -const OPTIMIZER = Xpress.Optimizer(OUTPUTLOG = 0) -const OPTIMIZER_2 = Xpress.Optimizer(OUTPUTLOG = 0) - -# Xpress can only obtain primal and dual rays without presolve. Check more on -# https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/XPRSgetprimalray.html - -const BRIDGED_OPTIMIZER = MOI.Bridges.full_bridge_optimizer( - OPTIMIZER, Float64) -const BRIDGED_CERTIFICATE_OPTIMIZER = - MOI.Bridges.full_bridge_optimizer( - Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0), Float64) -const CONFIG = MOIT.TestConfig() -const CONFIG_LOW_TOL = MOIT.TestConfig(atol = 1e-3, rtol = 1e-3) +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end +end -@testset "MOI interface" begin +function test_Basic_Parameters() optimizer = Xpress.Optimizer(OUTPUTLOG = 0) - @test MOI.get(optimizer, MOI.RawParameter("logfile")) == "" + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("logfile")) == "" optimizer = Xpress.Optimizer(OUTPUTLOG = 0, logfile = "output.log") - @test MOI.get(optimizer, MOI.RawParameter("logfile")) == "output.log" + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("logfile")) == "output.log" @test MOI.set(optimizer, MOI.TimeLimitSec(), 100) === nothing @test MOI.set(optimizer, MOI.TimeLimitSec(), 3600.0) === nothing - @test MOI.get(optimizer, MOI.RawParameter("MIPRELSTOP")) == 0.0001 - MOI.set(optimizer, MOI.RawParameter("MIPRELSTOP"), 0.123) - @test MOI.get(optimizer, MOI.RawParameter("MIPRELSTOP")) == 0.123 - - @test MOI.get(optimizer, MOI.RawParameter("MPSOBJNAME")) == "" - MOI.set(optimizer, MOI.RawParameter("MPSOBJNAME"), "qwerty") - @test MOI.get(optimizer, MOI.RawParameter("MPSOBJNAME")) == "qwerty" - - @test MOI.get(optimizer, MOI.RawParameter("PRESOLVE")) == 1 - MOI.set(optimizer, MOI.RawParameter("PRESOLVE"), 3) - @test MOI.get(optimizer, MOI.RawParameter("PRESOLVE")) == 3 - - @show MOI.get(optimizer, MOI.RawParameter("XPRESSVERSION")) - @test MOI.get(optimizer, MOI.RawParameter("MATRIXNAME")) == "" - @test MOI.get(optimizer, MOI.RawParameter("SUMPRIMALINF")) == 0.0 - @test MOI.get(optimizer, MOI.RawParameter("NAMELENGTH")) == 8 -end + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("MIPRELSTOP")) == 0.0001 + MOI.set(optimizer, MOI.RawOptimizerAttribute("MIPRELSTOP"), 0.123) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("MIPRELSTOP")) == 0.123 -@testset "SolverName" begin - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "Xpress" -end + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("MPSOBJNAME")) == "" + MOI.set(optimizer, MOI.RawOptimizerAttribute("MPSOBJNAME"), "qwerty") + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("MPSOBJNAME")) == "qwerty" -@testset "supports_default_copy_to" begin - @test MOIU.supports_default_copy_to(OPTIMIZER, true) -end + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("PRESOLVE")) == 1 + MOI.set(optimizer, MOI.RawOptimizerAttribute("PRESOLVE"), 3) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("PRESOLVE")) == 3 -@testset "Unit Tests Constraints" begin - MOIT.basic_constraint_tests(OPTIMIZER, CONFIG) + @show MOI.get(optimizer, MOI.RawOptimizerAttribute("XPRESSVERSION")) + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("MATRIXNAME")) == "" + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("SUMPRIMALINF")) == 0.0 + @test MOI.get(optimizer, MOI.RawOptimizerAttribute("NAMELENGTH")) == 8 + @test MOI.get(optimizer, MOI.SolverName()) == "Xpress" end -@testset "Unit Tests" begin - MOIT.unittest(BRIDGED_OPTIMIZER, CONFIG, #= excludes =# String[ - # TODO: fix errors - # does not work with caching optimizer - "delete_soc_variables", - - # These tests fail due to tolerance issues; tested below. - "solve_qcp_edge_cases", - "solve_qp_edge_cases", - "solve_qp_zero_offdiag", - - # These tests require extra parameters to obtain certificates. - "solve_farkas_equalto_upper", - "solve_farkas_equalto_lower", - "solve_farkas_lessthan", - "solve_farkas_greaterthan", - "solve_farkas_interval_upper", - "solve_farkas_interval_lower", - "solve_farkas_variable_lessthan", - "solve_farkas_variable_lessthan_max", - ], - ) - # certificates - MOIT.solve_farkas_equalto_upper(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_equalto_lower(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_lessthan(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_greaterthan(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_interval_upper(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_interval_lower(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_variable_lessthan(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - MOIT.solve_farkas_variable_lessthan_max(BRIDGED_CERTIFICATE_OPTIMIZER,CONFIG) - # tolerance - MOIT.solve_qcp_edge_cases(BRIDGED_OPTIMIZER, CONFIG_LOW_TOL) - MOIT.solve_qp_edge_cases(BRIDGED_OPTIMIZER, CONFIG_LOW_TOL) - MOIT.solve_qp_zero_offdiag(BRIDGED_OPTIMIZER, CONFIG_LOW_TOL) - # MOIT.delete_soc_variables(OPTIMIZER, CONFIG_LOW_TOL) - MOIT.modificationtest(BRIDGED_OPTIMIZER, CONFIG) -end +function test_runtests() -@testset "Linear tests" begin - MOIT.contlineartest( - BRIDGED_OPTIMIZER, - MOIT.TestConfig( - basis = true, - infeas_certificates = false - ), [ - # These tests require extra parameters to obtain certificates. - "linear8a", "linear8b", "linear8c","linear12", - ] - ) - MOIT.linear8atest(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) - MOIT.linear8btest(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) - MOIT.linear8ctest(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) - MOIT.linear12test(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) -end - -@testset "Quadratic tests" begin - MOIT.contquadratictest( - BRIDGED_OPTIMIZER, - MOIT.TestConfig(atol = 1e-3, rtol = 1e-3), - [ - # Xpress does NOT support ncqcp. - "ncqcp", + optimizer = Xpress.Optimizer(OUTPUTLOG = 0) + model = MOI.Bridges.full_bridge_optimizer(optimizer, Float64) + MOI.set(model, MOI.Silent(), true) + MOI.Test.runtests( + model, + MOI.Test.Config(atol = 1e-3, rtol = 1e-3), + exclude = String[ + # tested with PRESOLVE=0 below + "_SecondOrderCone_", + "test_constraint_PrimalStart_DualStart_SecondOrderCone", + "_RotatedSecondOrderCone_", + "_GeometricMeanCone_", + # Xpress cannot handle nonconvex quadratic constraint + "test_quadratic_nonconvex_", + ], + ) + + optimizer_no_presolve = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) + model = MOI.Bridges.full_bridge_optimizer(optimizer_no_presolve, Float64) + MOI.Test.runtests( + model, + MOI.Test.Config( + atol = 1e-3, + rtol = 1e-3, + exclude = Any[MOI.ConstraintDual, MOI.DualObjectiveValue], + ), + include = String[ + "_SecondOrderCone_", + "test_constraint_PrimalStart_DualStart_SecondOrderCone", + "_RotatedSecondOrderCone_", + "_GeometricMeanCone_" ] ) + return end -@testset "Conic tests" begin - MOIT.lintest(BRIDGED_OPTIMIZER, CONFIG, [ - # These tests require extra parameters to obtain certificates. - "lin3", "lin4" - ]) - MOIT.lin3test(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) - MOIT.lin4test(BRIDGED_CERTIFICATE_OPTIMIZER, CONFIG) - MOIT.soctest(BRIDGED_OPTIMIZER, MOIT.TestConfig(duals = false, atol=1e-3), ["soc3"]) - MOIT.soc3test( - BRIDGED_OPTIMIZER, - MOIT.TestConfig(duals = false, infeas_certificates = false, atol = 1e-3) - ) - MOIT.rsoctest(BRIDGED_OPTIMIZER, MOIT.TestConfig(duals = false, atol=1e-3), ["rotatedsoc3"]) - MOIT.rotatedsoc3test(BRIDGED_OPTIMIZER, MOIT.TestConfig(duals = false, atol=1e-2)) - MOIT.geomeantest(BRIDGED_OPTIMIZER, MOIT.TestConfig(duals = false, atol=1e-3)) -end - -@testset "Integer Linear tests" begin - MOIT.intlineartest(BRIDGED_OPTIMIZER, CONFIG) -end - -@testset "ModelLike tests" begin - @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(BRIDGED_OPTIMIZER) - end - - @testset "validtest" begin - MOIT.validtest(OPTIMIZER) - end - - @testset "emptytest" begin - MOIT.emptytest(BRIDGED_OPTIMIZER) - end - - @testset "orderedindicestest" begin - MOIT.orderedindicestest(OPTIMIZER) - end - - @testset "copytest" begin - BRIDGED_OPTIMIZER_2 = MOI.Bridges.full_bridge_optimizer( - OPTIMIZER_2, Float64 +function test_Conflicts() + @testset "Binary" begin + T = Float64 + model = Xpress.Optimizer(OUTPUTLOG = 0, DEFAULTALG = 3, PRESOLVE = 0) + x, c1 = MOI.add_constrained_variable(model, MOI.ZeroOne()) + c2 = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), [x]), zero(T)), + MOI.EqualTo(T(0.5)), ) - MOIT.copytest(BRIDGED_OPTIMIZER, BRIDGED_OPTIMIZER_2) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + zeroone_conflict = MOI.get(model, MOI.ConstraintConflictStatus(), c1) + @test zeroone_conflict == MOI.MAYBE_IN_CONFLICT || + zeroone_conflict == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT end -end - -@testset "IIS tests" begin for warning in [true, false] @testset "Variable bounds" begin model = Xpress.Optimizer(OUTPUTLOG = 0, DEFAULTALG = 3, PRESOLVE = 0) - MOI.set(model, MOI.RawParameter("MOI_WARNINGS"), warning) + MOI.set(model, MOI.RawOptimizerAttribute("MOI_WARNINGS"), warning) x = MOI.add_variable(model) - c1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(2.0)) - c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) + c1 = MOI.add_constraint(model, x, MOI.GreaterThan(2.0)) + c2 = MOI.add_constraint(model, x, MOI.LessThan(1.0)) # Getting the results before the conflict refiner has been called must return an error. @test MOI.get(model, MOI.ConflictStatus()) == MOI.COMPUTE_CONFLICT_NOT_CALLED @@ -292,23 +209,25 @@ end # Once it's called, no problem. MOI.compute_conflict!(model) - @test MOI.get(model, MOI.ConflictStatus()) == MOI.NO_CONFLICT_FOUND + @test MOI.get(model, MOI.ConflictStatus()) == MOI.NO_CONFLICT_EXISTS @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.NOT_IN_CONFLICT @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.NOT_IN_CONFLICT end end -@testset "test_farkas_dual_min" begin +# Xpress can only obtain primal and dual rays without presolve. Check more on +# https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/XPRSgetprimalray.html +function test_Farkas_Dual_Min() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) 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]), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + x[1], ) clb = MOI.add_constraint.( - model, MOI.SingleVariable.(x), MOI.GreaterThan(0.0) + model, x, MOI.GreaterThan(0.0) ) c = MOI.add_constraint( model, @@ -327,17 +246,17 @@ end @test clb_dual[2] ≈ -c_dual atol = 1e-6 end -@testset "test_farkas_dual_min_interval" begin +function test_Farkas_Dual_Min_Interval() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) 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]), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + x[1], ) clb = MOI.add_constraint.( - model, MOI.SingleVariable.(x), MOI.Interval(0.0, 10.0) + model, x, MOI.Interval(0.0, 10.0) ) c = MOI.add_constraint( model, @@ -356,16 +275,16 @@ end @test clb_dual[2] ≈ -c_dual atol = 1e-6 end -@testset "test_farkas_dual_min_equalto" begin +function test_Farkas_Dual_Min_Equalto() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) 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]), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + x[1], ) - clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.EqualTo(0.0)) + clb = MOI.add_constraint.(model, x, MOI.EqualTo(0.0)) c = MOI.add_constraint( model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), @@ -383,7 +302,7 @@ end @test clb_dual[2] ≈ -c_dual atol = 1e-6 end -@testset "test_farkas_dual_min_ii" begin +function test_Farkas_Dual_Min_2() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) x = MOI.add_variables(model, 2) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -393,7 +312,7 @@ end MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x[1])], 0.0), ) clb = MOI.add_constraint.( - model, MOI.SingleVariable.(x), MOI.LessThan(0.0) + model, x, MOI.LessThan(0.0) ) c = MOI.add_constraint( model, @@ -412,17 +331,17 @@ end @test clb_dual[2] ≈ c_dual atol = 1e-6 end -@testset "test_farkas_dual_max" begin +function test_Farkas_Dual_Max() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) 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]), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + x[1], ) clb = MOI.add_constraint.( - model, MOI.SingleVariable.(x), MOI.GreaterThan(0.0) + model, x, MOI.GreaterThan(0.0) ) c = MOI.add_constraint( model, @@ -441,7 +360,7 @@ end @test clb_dual[2] ≈ -c_dual atol = 1e-6 end -@testset "test_farkas_dual_max_ii" begin +function test_Farkas_Dual_Max_2() model = Xpress.Optimizer(OUTPUTLOG = 0, PRESOLVE = 0) x = MOI.add_variables(model, 2) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -451,7 +370,7 @@ end MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x[1])], 0.0), ) clb = MOI.add_constraint.( - model, MOI.SingleVariable.(x), MOI.LessThan(0.0) + model, x, MOI.LessThan(0.0) ) c = MOI.add_constraint( model, @@ -470,7 +389,7 @@ end @test clb_dual[2] ≈ c_dual atol = 1e-6 end -@testset "Delete equality constraint in binary variable" begin +function test_Delete_equality_constraint_in_binary_variable() atol = rtol = 1e-6 model = Xpress.Optimizer(OUTPUTLOG = 0) # an example on mixed integer programming @@ -492,16 +411,16 @@ end vc1 = MOI.add_constraint( model, - MOI.SingleVariable(v[1]), + v[1], MOI.Interval(0.0, 5.0), ) vc2 = MOI.add_constraint( model, - MOI.SingleVariable(v[2]), + v[2], MOI.Interval(0.0, 10.0), ) - vc3 = MOI.add_constraint(model, MOI.SingleVariable(v[2]), MOI.Integer()) - vc4 = MOI.add_constraint(model, MOI.SingleVariable(v[3]), MOI.ZeroOne()) + vc3 = MOI.add_constraint(model, v[2], MOI.Integer()) + vc4 = MOI.add_constraint(model, v[3], MOI.ZeroOne()) objf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.1, 2.0, 5.0], v), 0.0) @@ -534,7 +453,7 @@ end MOI.delete(model, vc4) # Fix z to its optimal value - vc5 = MOI.add_constraint(model, MOI.SingleVariable(v[3]), MOI.EqualTo(z_value)) + vc5 = MOI.add_constraint(model, v[3], MOI.EqualTo(z_value)) MOI.optimize!(model) @@ -553,7 +472,7 @@ end @test MOI.get(model, MOI.ObjectiveBound()) >= 19.4 - atol # Add back binary bounds to z - vc4 = MOI.add_constraint(model, MOI.SingleVariable(v[3]), MOI.ZeroOne()) + vc4 = MOI.add_constraint(model, v[3], MOI.ZeroOne()) # Remove equality constraint MOI.delete(model, vc5) @@ -573,36 +492,36 @@ end @test MOI.get(model, MOI.ObjectiveBound()) >= 19.4 - atol end -@testset "Binary Variables Infeasibility" begin +function test_Binary_Variables_Infeasibility() atol = rtol = 1e-6 model = Xpress.Optimizer(OUTPUTLOG = 0) v = MOI.add_variable(model) infeas_err = ErrorException("The problem is infeasible") - vc1 = MOI.add_constraint(model, MOI.SingleVariable(v), MOI.ZeroOne()) + vc1 = MOI.add_constraint(model, v, MOI.ZeroOne()) @test_throws infeas_err vc2 = MOI.add_constraint( model, - MOI.SingleVariable(v), + v, MOI.GreaterThan(2.0), ) @test_throws infeas_err vc3 = MOI.add_constraint( model, - MOI.SingleVariable(v), + v, MOI.LessThan(-1.0), - ) + ) @test_throws infeas_err vc4 = MOI.add_constraint( model, - MOI.SingleVariable(v), + v, MOI.Interval(-1.0,-0.5), ) @test_throws infeas_err vc5 = MOI.add_constraint( model, - MOI.SingleVariable(v), + v, MOI.EqualTo(2.0), ) end -@testset "MIP Start testing" begin +function test_MIP_Start() # The idea of the test is the following: # * Give Xpress a problem it can solve quickly but not simple enough to be # solved at the root node when heuristics and cuts are disabled. @@ -671,13 +590,13 @@ end ) @test isapprox(9945.0, computed_obj_value; rtol = rtol, atol = atol) - node_solution_was_found = MOI.get(model, MOI.RawParameter("MIPSOLNODE")) + node_solution_was_found = MOI.get(model, MOI.RawOptimizerAttribute("MIPSOLNODE")) @test node_solution_was_found > 1 # SECOND RUN: run without MIP-start and only searching the first node. # Should give a worse solution than the previous one. - MOI.set(model, MOI.RawParameter("MAXNODE"), 1) + MOI.set(model, MOI.RawOptimizerAttribute("MAXNODE"), 1) MOI.optimize!(model) @@ -716,3 +635,6 @@ end @test isapprox(9945.0, computed_obj_value3; rtol = rtol, atol = atol) end +end + +TestMOIWrapper.runtests() \ No newline at end of file diff --git a/test/SemiContInt/semicontint.jl b/test/SemiContInt/semicontint.jl index dea650b9..6e3c9cce 100644 --- a/test/SemiContInt/semicontint.jl +++ b/test/SemiContInt/semicontint.jl @@ -1,11 +1,11 @@ -function semiconttest(model::MOI.ModelLike, config::MOIT.TestConfig{T}) where T +function semiconttest(model::MOI.ModelLike, config::MOIT.Config{T}) where T atol = config.atol rtol = config.rtol @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) @test MOI.supports(model, MOI.ObjectiveSense()) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.MathOptInterface.Semicontinuous{T}) + @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.MathOptInterface.Semicontinuous{T}) # 2 variables # min x @@ -19,11 +19,11 @@ function semiconttest(model::MOI.ModelLike, config::MOIT.TestConfig{T}) where T v = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - vc1 = MOI.add_constraint(model, MOI.SingleVariable(v[1]), MOI.Semicontinuous(T(2), T(3))) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.Semicontinuous{T}}()) == 1 + vc1 = MOI.add_constraint(model, v[1], MOI.Semicontinuous(T(2), T(3))) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex,MOI.Semicontinuous{T}}()) == 1 - vc2 = MOI.add_constraint(model, MOI.SingleVariable(v[2]), MOI.EqualTo(zero(T))) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.EqualTo{T}}()) == 1 + vc2 = MOI.add_constraint(model, v[2], MOI.EqualTo(zero(T))) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex,MOI.EqualTo{T}}()) == 1 cf = MOI.ScalarAffineFunction{T}(MOI.ScalarAffineTerm{T}.([one(T), -one(T)], v), zero(T)) c = MOI.add_constraint(model, cf, MOI.GreaterThan(zero(T))) @@ -148,14 +148,14 @@ function semiconttest(model::MOI.ModelLike, config::MOIT.TestConfig{T}) where T end end -function semiinttest(model::MOI.ModelLike, config::MOIT.TestConfig{T}) where T +function semiinttest(model::MOI.ModelLike, config::MOIT.Config{T}) where T atol = config.atol rtol = config.rtol @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) @test MOI.supports(model, MOI.ObjectiveSense()) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.MathOptInterface.Semiinteger{T}) + @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.MathOptInterface.Semiinteger{T}) # 2 variables # min x @@ -169,11 +169,11 @@ function semiinttest(model::MOI.ModelLike, config::MOIT.TestConfig{T}) where T v = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - vc1 = MOI.add_constraint(model, MOI.SingleVariable(v[1]), MOI.Semiinteger(T(2), T(3))) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.Semiinteger{T}}()) == 1 + vc1 = MOI.add_constraint(model, v[1], MOI.Semiinteger(T(2), T(3))) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex,MOI.Semiinteger{T}}()) == 1 - vc2 = MOI.add_constraint(model, MOI.SingleVariable(v[2]), MOI.EqualTo(zero(T))) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.EqualTo{T}}()) == 1 + vc2 = MOI.add_constraint(model, v[2], MOI.EqualTo(zero(T))) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex,MOI.EqualTo{T}}()) == 1 cf = MOI.ScalarAffineFunction{T}(MOI.ScalarAffineTerm{T}.([one(T), -one(T)], v), zero(T)) c = MOI.add_constraint(model, cf, MOI.GreaterThan(zero(T))) diff --git a/test/runtests.jl b/test/runtests.jl index d216a74f..e2224782 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,7 +5,9 @@ println(Xpress.getbanner()) println("Optimizer version: $(Xpress.getversion())") @testset "$(folder)" for folder in [ - "MathOptInterface", "xprs_callbacks", "Derivative" + "MathOptInterface", + "xprs_callbacks", + "Derivative", ] @testset "$(file)" for file in readdir(folder) include(joinpath(folder, file))