diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dd4803..1d2c413 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - version: '1' os: ubuntu-latest arch: x64 - - version: '1.0' + - version: '1.6' os: ubuntu-latest arch: x64 steps: diff --git a/Project.toml b/Project.toml index e2a0050..6cf9a57 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,14 @@ name = "QuadraticToBinary" uuid = "014a38d5-7acb-4e20-b6c0-4fe5c2344fd1" authors = ["Joaquim Garcia "] -version = "0.2.4" +version = "0.3.0" [deps] MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" [compat] -MathOptInterface = "0.9.12" +MathOptInterface = "0.10.6" DataStructures = "0.17.10, 0.18.0" julia = "1" diff --git a/README.md b/README.md index 714eeab..7d26b97 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ MIP solvers such as [Cbc](https://github.com/JuliaOpt/Cbc.jl), [CPLEX](https://g ## Example -If one wants to solve the optimization problem with the package: +If one wants to solve the optimization problem with this package: ```julia # Max 2x + y @@ -32,9 +32,9 @@ If one wants to solve the optimization problem with the package: One should model as a quadratic program and simply wrap the solver with a `QuadraticToBinary.Optimizer`, with one extra requirement: all variables appearing -in quadratic terms must be bounded above an below. +in quadratic terms must be bounded above and below. -Therefore the new model can be: +Therefore, the new model can be: ```julia @@ -52,17 +52,9 @@ using MathOptInterface using QuadraticToBinary using Cbc -const MOI = MathOptInterface -const MOIU = MOI.Utilities - -const optimizer = MOI.Bridges.full_bridge_optimizer( - MOIU.CachingOptimizer( - MOIU.UniversalFallback(MOIU.Model{Float64}()), - Cbc.Optimizer()), Float64) - model = Model( ()->QuadraticToBinary.Optimizer{Float64}( - optimizer)) + MOI.instantiate(Cbc.Optimizer, with_bridge_type = Float64))) @variable(model, 1 <= x <= 10) @variable(model, 1 <= y <= 10) @@ -79,10 +71,10 @@ primal_status(model) objective_value(model) # ≈ 9.0 -value(x) # ≈ 4.0 -value(y) # ≈ 1.0 +@assert value(x) ≈ 4.0 +@assert value(y) ≈ 1.0 -value(c) # ≈ 4.0 +@assert value(c) ≈ 4.0 ``` ### MathOptInterface with Cbc solver @@ -91,49 +83,47 @@ value(c) # ≈ 4.0 using MathOptInterface using QuadraticToBinary const MOI = MathOptInterface -const MOIU = MOI.Utilities using Cbc -const optimizer = MOI.Bridges.full_bridge_optimizer( - MOIU.CachingOptimizer( - MOIU.UniversalFallback(MOIU.Model{Float64}()), - Cbc.Optimizer()), Float64) +optimizer = MOI.instantiate(Cbc.Optimizer, with_bridge_type = Float64) model = QuadraticToBinary.Optimizer{Float64}(optimizer) x = MOI.add_variable(model) y = MOI.add_variable(model) -MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) -MOI.add_constraint(model, MOI.SingleVariable(y), MOI.GreaterThan(1.0)) +MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) +MOI.add_constraint(model, y, MOI.GreaterThan(1.0)) -MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(10.0)) -MOI.add_constraint(model, MOI.SingleVariable(y), MOI.LessThan(10.0)) +MOI.add_constraint(model, x, MOI.LessThan(10.0)) +MOI.add_constraint(model, y, MOI.LessThan(10.0)) -cf = MOI.ScalarQuadraticFunction( - [MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(1.0, x, y)], 0.0) -c = MOI.add_constraint(model, cf, MOI.LessThan(4.0)) +c = MOI.add_constraint(model, 1.0 * x * y, MOI.LessThan(4.0)) MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], [x, y]), 0.0)) + 2.0 * x + y) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) MOI.optimize!(model) -MOI.get(model, MOI.TerminationStatus()) # config.optimal_status +@assert MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL -MOI.get(model, MOI.PrimalStatus()) # MOI.FEASIBLE_POINT +@assert MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT -MOI.get(model, MOI.ObjectiveValue()) # ≈ 9.0 +@assert MOI.get(model, MOI.ObjectiveValue()) ≈ 9.0 -MOI.get(model, MOI.VariablePrimal(), x) # ≈ 4.0 -MOI.get(model, MOI.VariablePrimal(), y) # ≈ 1.0 +@assert MOI.get(model, MOI.VariablePrimal(), x) ≈ 4.0 +@assert MOI.get(model, MOI.VariablePrimal(), y) ≈ 1.0 -MOI.get(model, MOI.ConstraintPrimal(), c) # ≈ 4.0 +@assert MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 4.0 ``` Note that duals are not available because the problem was approximated as a MIP. +## QuadraticToBinary.Optimizer Attributes + +### Precision + It is possible to change the precision of the approximations to the number `val`, for all variables: @@ -150,6 +140,8 @@ MOI.set(model, QuadraticToBinary.VariablePrecision(), vi, val) The precision for each varible will be `val * (UB - LB)`. Where `UB` and `LB` are, respectively, the upper and lower bound of the variable. +### Bounds + For the sake of simplicity, the following two attributes are made available: `QuadraticToBinary.FallbackUpperBound` and `QuadraticToBinary.FallbackLowerBound`. As usual, these can be get and set with the `MOI.get` and `MOI.set` methods. diff --git a/src/moi.jl b/src/moi.jl index bccfa22..9357eba 100644 --- a/src/moi.jl +++ b/src/moi.jl @@ -4,7 +4,6 @@ const MOIU = MOI.Utilities const VI = MOI.VariableIndex const CI = MOI.ConstraintIndex -const SV = MOI.SingleVariable const SAF{T} = MOI.ScalarAffineFunction{T} const EQ{T} = MOI.EqualTo{T} const LT{T} = MOI.LessThan{T} @@ -61,7 +60,7 @@ mutable struct VariableInfo end # Supported Functions -const SF = Union{MOI.SingleVariable, +const SF = Union{MOI.VariableIndex, MOI.ScalarAffineFunction{Float64}, MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}} @@ -84,18 +83,18 @@ struct IndexDataCache{T} sa_eq::Vector{CI{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}} sa_lt::Vector{CI{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}} sa_gt::Vector{CI{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}} - sv_lt::Vector{CI{MOI.SingleVariable, MOI.LessThan{T}}} - sv_gt::Vector{CI{MOI.SingleVariable, MOI.GreaterThan{T}}} - sv_zo::Vector{CI{MOI.SingleVariable, MOI.ZeroOne}} + sv_lt::Vector{CI{MOI.VariableIndex, MOI.LessThan{T}}} + sv_gt::Vector{CI{MOI.VariableIndex, MOI.GreaterThan{T}}} + sv_zo::Vector{CI{MOI.VariableIndex, MOI.ZeroOne}} function IndexDataCache{T}() where T new( VI[], CI{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}[], CI{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}[], CI{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}[], - CI{MOI.SingleVariable, MOI.LessThan{T}}[], - CI{MOI.SingleVariable, MOI.GreaterThan{T}}[], - CI{MOI.SingleVariable, MOI.ZeroOne}[], + CI{MOI.VariableIndex, MOI.LessThan{T}}[], + CI{MOI.VariableIndex, MOI.GreaterThan{T}}[], + CI{MOI.VariableIndex, MOI.ZeroOne}[], ) end end @@ -132,7 +131,11 @@ mutable struct Optimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer allow_soc::Bool - function Optimizer{T}(optimizer::OT; lb = -Inf, ub = +Inf, global_precision = 1e-4 + function Optimizer{T}( + optimizer::OT; + lb = -Inf, + ub = +Inf, + global_precision = 1e-4 ) where {T, OT <: MOI.ModelLike} # TODO optimizer must support binary, and affine in less and greater return new{T, OT}( @@ -226,6 +229,9 @@ end # MOI.supports(model::Optimizer, args...) = MOI.supports(model.optimizer, args...) +# TODO, call this on inner solver +MOI.supports_incremental_interface(::Optimizer) = true + function MOI.supports(model::Optimizer, attr::MOI.VariableName, tp::Type{MOI.VariableIndex}) MOI.supports(model.optimizer, attr, tp) end @@ -244,7 +250,7 @@ function MOI.supports(model::Optimizer, attr::Union{ MOI.Silent, MOI.NumberOfThreads, MOI.TimeLimitSec, - MOI.RawParameter, + MOI.RawOptimizerAttribute, } ) return MOI.supports(model.optimizer, attr) @@ -254,7 +260,7 @@ function MOI.get(model::Optimizer, attr::Union{ MOI.Silent, MOI.NumberOfThreads, MOI.TimeLimitSec, - MOI.RawParameter, + MOI.RawOptimizerAttribute, } ) return MOI.get(model.optimizer, attr) @@ -271,7 +277,7 @@ end function MOI.supports(model::Optimizer, attr::Union{MOI.ObjectiveSense, - MOI.ObjectiveFunction{MOI.SingleVariable}, + MOI.ObjectiveFunction{MOI.VariableIndex}, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}, }) where T return MOI.supports(model.optimizer, attr) @@ -334,12 +340,12 @@ function MOI.supports_add_constrained_variables( return MOI.supports_add_constrained_variables(model.optimizer, S) end -# function MOI.set(model::Optimizer, param::MOI.RawParameter, value) +# function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value) # # if in a subset of the q2b save it # # otherwise pass it forward # end -# function MOI.get(model::Optimizer, param::MOI.RawParameter) +# function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute) # end function MOI.Utilities.supports_default_copy_to(model::Optimizer, val::Bool) @@ -408,7 +414,7 @@ end function MOI.set( model::Optimizer, attr::MOI.ObjectiveFunction{F}, f::F ) where {F <: Union{ - MOI.SingleVariable, + MOI.VariableIndex, MOI.ScalarAffineFunction{T} }} where T model.quad_obj = nothing @@ -435,8 +441,8 @@ function MOI.get( MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}() ) MOI.ScalarQuadraticFunction{T}( - f.terms, MOI.ScalarQuadraticTerm{T}[], + f.terms, f.constant ) else @@ -449,7 +455,7 @@ function MOI.get( F } ) where F <: Union{ - MOI.SingleVariable, + MOI.VariableIndex, MOI.ScalarAffineFunction{T} } where T if model.quad_obj !== nothing @@ -642,7 +648,7 @@ function quad_index(ci::MOI.ConstraintIndex{F, S}, end ## -## SingleVariable-in-Set +## VariableIndex-in-Set ## _bounds(s::MOI.GreaterThan{Float64}) = (s.lower, Inf) @@ -696,28 +702,28 @@ function _throw_if_existing_upper( end end function cache_bounds( - model::Optimizer, f::MOI.SingleVariable, s::MOI.LessThan{T}) where T - info = model.original_variables[f.variable] - _throw_if_existing_upper(info.bound, info.type, MOI.LessThan{T}, f.variable) + model::Optimizer, f::MOI.VariableIndex, s::MOI.LessThan{T}) where T + info = model.original_variables[f] + _throw_if_existing_upper(info.bound, info.type, MOI.LessThan{T}, f) info.bound = info.bound == GREATER_THAN ? LESS_AND_GREATER_THAN : LESS_THAN lb, ub = _bounds(s) info.upper = ub return end function cache_bounds( - model::Optimizer, f::MOI.SingleVariable, s::MOI.GreaterThan{T}) where T - info = model.original_variables[f.variable] - _throw_if_existing_lower(info.bound, info.type, MOI.GreaterThan{T}, f.variable) + model::Optimizer, f::MOI.VariableIndex, s::MOI.GreaterThan{T}) where T + info = model.original_variables[f] + _throw_if_existing_lower(info.bound, info.type, MOI.GreaterThan{T}, f) info.bound = info.bound == LESS_THAN ? LESS_AND_GREATER_THAN : GREATER_THAN lb, ub = _bounds(s) info.lower = lb return end function cache_bounds( - model::Optimizer, f::MOI.SingleVariable, s::MOI.EqualTo{T}) where T - info = model.original_variables[f.variable] - _throw_if_existing_lower(info.bound, info.type, MOI.EqualTo{T}, f.variable) - _throw_if_existing_upper(info.bound, info.type, MOI.EqualTo{T}, f.variable) + model::Optimizer, f::MOI.VariableIndex, s::MOI.EqualTo{T}) where T + info = model.original_variables[f] + _throw_if_existing_lower(info.bound, info.type, MOI.EqualTo{T}, f) + _throw_if_existing_upper(info.bound, info.type, MOI.EqualTo{T}, f) info.bound = EQUAL_TO lb, ub = _bounds(s) info.lower = lb @@ -725,10 +731,10 @@ function cache_bounds( return end function cache_bounds( - model::Optimizer, f::MOI.SingleVariable, s::MOI.Interval{T}) where T - info = model.original_variables[f.variable] - _throw_if_existing_lower(info.bound, info.type, MOI.Interval{T}, f.variable) - _throw_if_existing_upper(info.bound, info.type, MOI.Interval{T}, f.variable) + model::Optimizer, f::MOI.VariableIndex, s::MOI.Interval{T}) where T + info = model.original_variables[f] + _throw_if_existing_lower(info.bound, info.type, MOI.Interval{T}, f) + _throw_if_existing_upper(info.bound, info.type, MOI.Interval{T}, f) info.bound = INTERVAL lb, ub = _bounds(s) info.lower = lb @@ -745,10 +751,9 @@ function MOI.add_constrained_variables( model.original_variables[v] = VariableInfo() end # from add con - f = MOI.SingleVariable.(vs) - for i in eachindex(f) - cache_bounds(model, f[i], s[i]) - model.ci_to_var[c[i]] = f[i].variable + for i in eachindex(vs) + cache_bounds(model, vs[i], s[i]) + model.ci_to_var[c[i]] = vs[i] end return vs, c end @@ -759,40 +764,39 @@ function MOI.add_constrained_variable( # from add var model.original_variables[v] = VariableInfo() # from add con - f = MOI.SingleVariable(v) - cache_bounds(model, f, s) - model.ci_to_var[ci] = f.variable + cache_bounds(model, v, s) + model.ci_to_var[ci] = v return v, ci 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} c = MOI.add_constraints(model.optimizer, f, s) for i in eachindex(f) cache_bounds(model, f[i], s[i]) - model.ci_to_var[c[i]] = f[i].variable + model.ci_to_var[c[i]] = f[i] end return c end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, s::S + model::Optimizer, f::MOI.VariableIndex, s::S ) where {S <: SCALAR_SETS} ci = MOI.add_constraint(model.optimizer, f, s) cache_bounds(model, f, s) - model.ci_to_var[ci] = f.variable + model.ci_to_var[ci] = f return ci 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 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<:Union{MOI.Interval{T}, MOI.EqualTo{T}} where T MOI.throw_if_not_valid(model.optimizer, c) MOI.set(model.optimizer, MOI.ConstraintSet(), c, s) @@ -805,7 +809,7 @@ function MOI.set( 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<:Union{MOI.LessThan{T}} where T MOI.throw_if_not_valid(model.optimizer, c) MOI.set(model.optimizer, MOI.ConstraintSet(), c, s) @@ -817,7 +821,7 @@ function MOI.set( 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<:Union{MOI.GreaterThan{T}} where T MOI.throw_if_not_valid(model.optimizer, c) MOI.set(model.optimizer, MOI.ConstraintSet(), c, s) @@ -830,7 +834,7 @@ end function MOI.delete(model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}} ) where T MOI.throw_if_not_valid(model.optimizer, c) var = model.ci_to_var[c] @@ -842,7 +846,7 @@ function MOI.delete(model::Optimizer, return end function MOI.delete(model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}} ) where T MOI.throw_if_not_valid(model.optimizer, c) var = model.ci_to_var[c] @@ -854,7 +858,7 @@ function MOI.delete(model::Optimizer, return end function MOI.delete(model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where S<:Union{MOI.Interval{T}, MOI.EqualTo{T}} where T MOI.throw_if_not_valid(model.optimizer, c) var = model.ci_to_var[c] @@ -879,13 +883,10 @@ function MOI.add_constrained_variables( model.original_variables[v] = VariableInfo() end # from add con - f = MOI.SingleVariable.(vs) - for i in eachindex(f) - info = model.original_variables[f[i].variable] + for i in eachindex(vs) + info = model.original_variables[vs[i]] info.type = scalar_type(S) - end - for i in eachindex(c) - model.ci_to_var[c[i]] = f[i].variable + model.ci_to_var[c[i]] = vs[i] end return vs, c end @@ -896,45 +897,44 @@ function MOI.add_constrained_variable( # from add var model.original_variables[v] = VariableInfo() # from add con - f = MOI.SingleVariable(v) - info = model.original_variables[f.variable] + info = model.original_variables[v] info.type = scalar_type(S) - model.ci_to_var[ci] = f.variable + model.ci_to_var[ci] = v return v, ci 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_TYPES} c = MOI.add_constraints(model.optimizer, f, s) for i in eachindex(f) - info = model.original_variables[f[i].variable] + info = model.original_variables[f[i]] if info.type != BINARY && info.type != INTEGER info.type = scalar_type(S) else - error("Variable $(f[i].variable) is already of type $(info.type)") + error("Variable $(f[i]) is already of type $(info.type)") end end for i in eachindex(c) - model.ci_to_var[c[i]] = f[i].variable + model.ci_to_var[c[i]] = f[i] end return c end function MOI.add_constraint( - model::Optimizer, f::MOI.SingleVariable, s::S + model::Optimizer, f::MOI.VariableIndex, s::S ) where {S <: SCALAR_TYPES} ci = MOI.add_constraint(model.optimizer, f, s) - info = model.original_variables[f.variable] + info = model.original_variables[f] if info.type != BINARY && info.type != INTEGER info.type = scalar_type(S) else - error("Variable $(f.variable) is already of type $(info.type)") + error("Variable $(f) is already of type $(info.type)") end - model.ci_to_var[ci] = f.variable + model.ci_to_var[ci] = f return ci end function MOI.delete(model::Optimizer, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where S<:SCALAR_TYPES MOI.throw_if_not_valid(model.optimizer, c) var = model.ci_to_var[c] @@ -969,19 +969,19 @@ function add_and_cache_constraints(ch, optimizer, return cis end function add_and_cache_constraints(ch, optimizer, - f::Vector{MOI.SingleVariable}, s::Vector{MOI.LessThan{T}}) where T + f::Vector{MOI.VariableIndex}, s::Vector{MOI.LessThan{T}}) where T cis = MOI.add_constraints(optimizer, f, s) append!(ch.sv_lt, cis) return cis end function add_and_cache_constraints(ch, optimizer, - f::Vector{MOI.SingleVariable}, s::Vector{MOI.GreaterThan{T}}) where T + f::Vector{MOI.VariableIndex}, s::Vector{MOI.GreaterThan{T}}) where T cis = MOI.add_constraints(optimizer, f, s) append!(ch.sv_gt, cis) return cis end function add_and_cache_constraints(ch, optimizer, - f::Vector{MOI.SingleVariable}, s::Vector{MOI.ZeroOne}) + f::Vector{MOI.VariableIndex}, s::Vector{MOI.ZeroOne}) cis = MOI.add_constraints(optimizer, f, s) append!(ch.sv_zo, cis) return cis @@ -1195,12 +1195,12 @@ function build_approximation!(model::Optimizer) c29 = add_and_cache_constraints(ch, model.optimizer, f29, s29) # eq 30 - bound on \Delta xj - f30a = SV[] - f30b = SV[] + f30a = MOI.VariableIndex[] + f30b = MOI.VariableIndex[] s30b = LT{T}[] for xj in DS - push!(f30a, MOI.SingleVariable(Δx[xj])) - push!(f30b, MOI.SingleVariable(Δx[xj])) + push!(f30a, Δx[xj]) + push!(f30b, Δx[xj]) push!(s30b, MOI.LessThan{T}(T(2)^(-T(get_precision(model, xj))))) end s30a = MOI.GreaterThan{T}[MOI.GreaterThan{T}(zero(T)) for i in eachindex(f30a)] @@ -1325,10 +1325,10 @@ function build_approximation!(model::Optimizer) c34b = add_and_cache_constraints(ch, model.optimizer, f34b, s34b) # 37 - z is binary - f37 = SV[] + f37 = MOI.VariableIndex[] s37 = MOI.ZeroOne[] for zj in values(z), zjl in zj - push!(f37, MOI.SingleVariable(zjl)) + push!(f37, zjl) push!(s37, MOI.ZeroOne()) end c37 = add_and_cache_constraints(ch, model.optimizer, f37, s37) @@ -1393,7 +1393,7 @@ function MOI.get( attr::MOI.ListOfConstraintIndices{F, S} ) where {S, F<:Union{ MOI.VectorOfVariables, - MOI.SingleVariable, + MOI.VariableIndex, }} return MOI.get(model.optimizer, attr) end @@ -1520,9 +1520,9 @@ function MOI.get(model::Optimizer, ::MOI.NumberOfConstraints{F, S}) where {F, S} return length(MOI.get(model, MOI.ListOfConstraintIndices{F,S}())) end -function MOI.get(model::Optimizer, ::MOI.ListOfConstraints) +function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) constraints = Set{Tuple{DataType, DataType}}() - inner_ctrs = MOI.get(model.optimizer, MOI.ListOfConstraints()) + inner_ctrs = MOI.get(model.optimizer, MOI.ListOfConstraintTypesPresent()) for (F, S) in inner_ctrs if F <: Union{MOI.ScalarAffineFunction, MOI.VectorQuadraticFunction} if MOI.get(model, MOI.NumberOfConstraints{F, S}()) > 0 @@ -1541,7 +1541,7 @@ function MOI.get(model::Optimizer, ::MOI.ListOfConstraints) end function ordered_term_indices(t::MOI.ScalarQuadraticTerm) - return VI.(minmax(t.variable_index_1.value, t.variable_index_2.value)) + return VI.(minmax(t.variable_1.value, t.variable_2.value)) end function ordered_term_indices(t::Union{MOI.VectorAffineTerm, MOI.VectorQuadraticTerm}) return (t.output_index, ordered_term_indices(t.scalar_term)...) diff --git a/test/moi.jl b/test/moi.jl index 8f83612..2728218 100644 --- a/test/moi.jl +++ b/test/moi.jl @@ -1,5 +1,5 @@ -function ncqcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function ncqcp1test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Max 2x + y @@ -17,17 +17,17 @@ function ncqcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) y = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - vc1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) + vc1 = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) @test vc1.value == x.value - vc2 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.GreaterThan(1.0)) + vc2 = MOI.add_constraint(model, y, MOI.GreaterThan(1.0)) @test vc2.value == y.value - vc3 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(10.0)) + vc3 = MOI.add_constraint(model, x, MOI.LessThan(10.0)) @test vc3.value == x.value - vc4 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.LessThan(10.0)) + vc4 = MOI.add_constraint(model, y, MOI.LessThan(10.0)) @test vc4.value == y.value - cf = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(1.0, x, y)], 0.0) + cf = 1.0 * x * y c = MOI.add_constraint(model, cf, MOI.LessThan(4.0)) @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 @@ -59,7 +59,7 @@ function ncqcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function ncqcp1test_mod2(model::MOI.ModelLike, config::MOIT.TestConfig) +function ncqcp1test_mod2(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Max 2x + y @@ -87,13 +87,12 @@ function ncqcp1test_mod2(model::MOI.ModelLike, config::MOIT.TestConfig) MOI.set(model, QuadraticToBinary.FallbackLowerBound(), nothing) @test MOI.get(model, QuadraticToBinary.FallbackLowerBound()) == -Inf - vc1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) + vc1 = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) @test vc1.value == x.value - vc2 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.GreaterThan(1.0)) + vc2 = MOI.add_constraint(model, y, MOI.GreaterThan(1.0)) @test vc2.value == y.value - cf = MOI.ScalarQuadraticFunction( - [MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(1.0, x, y)], 0.0) + cf = 1.0 * x * y c = MOI.add_constraint(model, cf, MOI.LessThan(4.0)) @test MOI.get(model, MOI.NumberOfConstraints{ MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 @@ -127,7 +126,7 @@ function ncqcp1test_mod2(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function ncqcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function ncqcp2test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Find x, y @@ -145,22 +144,22 @@ function ncqcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) y = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - vc1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) + vc1 = MOI.add_constraint(model, x, MOI.GreaterThan(0.0)) @test vc1.value == x.value - vc2 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.GreaterThan(0.0)) + vc2 = MOI.add_constraint(model, y, MOI.GreaterThan(0.0)) @test vc2.value == y.value # begin new - vc3 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(10.0)) + vc3 = MOI.add_constraint(model, x, MOI.LessThan(10.0)) @test vc3.value == x.value - vc4 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.LessThan(10.0)) + vc4 = MOI.add_constraint(model, y, MOI.LessThan(10.0)) @test vc4.value == y.value # end new - cf = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(1.0, x, y)], 0.0) + cf = 1.0 * x * y c = MOI.add_constraint(model, cf, MOI.EqualTo(4.0)) - cf2 = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(2.0, x, x)], 0.0) + cf2 = 1.0 * x * x c2 = MOI.add_constraint(model, cf2, MOI.EqualTo(4.0)) @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.EqualTo{Float64}}()) == 2 @@ -189,7 +188,7 @@ function ncqcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function qp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qp1test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # homogeneous quadratic objective @@ -207,28 +206,35 @@ function qp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) v = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == 3 + x = v[1] + y = v[2] + z = v[3] # begin new - for x in v - lb = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(-10.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(10.0)) - @test lb.value == x.value - @test ub.value == x.value - end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 3 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 3 + for _v in v + lb = MOI.add_constraint(model, _v, MOI.GreaterThan(-10.0)) + ub = MOI.add_constraint(model, _v, MOI.LessThan(10.0)) + @test lb.value == _v.value + @test ub.value == _v.value + end + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 3 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 3 # end new - cf1 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,2.0,3.0], v), 0.0) + cf1 = 1.0 * x + 2.0 * y + 3.0 * z c1 = MOI.add_constraint(model, cf1, MOI.GreaterThan(4.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 - c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [v[1],v[2]]), 0.0), MOI.GreaterThan(1.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 2 + c2 = MOI.add_constraint(model, 1.0 * x + y, MOI.GreaterThan(1.0)) + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 2 MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE - obj = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[], MOI.ScalarQuadraticTerm.([2.0, 1.0, 2.0, 1.0, 2.0], v[[1,1,2,2,3]], v[[1,2,2,3,3]]), 0.0) + obj = 1.0 * x * x + 1.0 * x * y + 1.0 * y * y + 1.0 * y * z + 1.0 * z * z MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj) if config.query @@ -264,7 +270,7 @@ function qp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function qp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qp2test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Same as `qp1` but with duplicate terms then change the objective and sense @@ -283,28 +289,37 @@ function qp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) v = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == 3 + x = v[1] + y = v[2] + z = v[3] # begin new - for x in v - lb = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(-10.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(10.0)) - @test lb.value == x.value - @test ub.value == x.value - end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 3 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 3 + for _v in v + lb = MOI.add_constraint(model, _v, MOI.GreaterThan(-10.0)) + ub = MOI.add_constraint(model, _v, MOI.LessThan(10.0)) + @test lb.value == _v.value + @test ub.value == _v.value + end + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 3 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 3 # end new - c1f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,2.0,3.0], v), 0.0) + c1f = 1.0 * x + 2.0 * y + 3.0 * z c1 = MOI.add_constraint(model, c1f, MOI.GreaterThan(4.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 - c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [v[1],v[2]]), 0.0), MOI.GreaterThan(1.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 2 + c2f = 1.0 * x + y + c2 = MOI.add_constraint(model, c2f, MOI.GreaterThan(1.0)) + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 2 MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE - obj = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.(0.0, v), MOI.ScalarQuadraticTerm.([2.0, 0.5, 0.5, 2.0, 1.0, 1.0, 1.0], [v[1], v[1], v[1], v[2], v[2], v[3], v[3]], [v[1], v[2], v[2], v[2], v[3], v[3], v[3]]), 0.0) + obj = MOI.ScalarQuadraticFunction( + MOI.ScalarQuadraticTerm.([2.0, 0.5, 0.5, 2.0, 1.0, 1.0, 1.0], [v[1], v[1], v[1], v[2], v[2], v[3], v[3]], [v[1], v[2], v[2], v[2], v[3], v[3], v[3]]), + MOI.ScalarAffineTerm.(0.0, v), + 0.0) MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj) if config.query @@ -340,7 +355,13 @@ function qp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - obj2 = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.(0.0, v), MOI.ScalarQuadraticTerm.([-4.0, -1.0, -1.0, -4.0, -2.0, -2.0, -2.0], [v[1], v[1], v[1], v[2], v[2], v[3], v[3]], [v[1], v[2], v[2], v[2], v[3], v[3], v[3]]), 0.0) + obj2 = MOI.ScalarQuadraticFunction( + MOI.ScalarQuadraticTerm.( + [-4.0, -1.0, -1.0, -4.0, -2.0, -2.0, -2.0], + [v[1], v[1], v[1], v[2], v[2], v[3], v[3]], + [v[1], v[2], v[2], v[2], v[3], v[3], v[3]]), + MOI.ScalarAffineTerm.(0.0, v), + 0.0) MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj2) if config.query @@ -366,7 +387,7 @@ function qp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function qp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qp3test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # non-homogeneous quadratic objective @@ -376,7 +397,7 @@ function qp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}()) - MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{Float64}) + MOI.supports_constraint(model, MOI.VariableIndex, MOI.GreaterThan{Float64}) MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) MOI.empty!(model) @@ -387,28 +408,28 @@ function qp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # begin new for w in [x, y] - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(10.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(10.0)) @test ub.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 2 # end new c1 = MOI.add_constraint(model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [x,y]), 0.0), + 1.0 * x + y, MOI.EqualTo(1.0) ) - vc1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) - # We test this after the creation of every `SingleVariable` constraint + vc1 = MOI.add_constraint(model, x, MOI.GreaterThan(0.0)) + # We test this after the creation of every `VariableIndex` constraint # to ensure a good coverage of corner cases. @test vc1.value == x.value - vc2 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.GreaterThan(0.0)) + vc2 = MOI.add_constraint(model, y, MOI.GreaterThan(0.0)) @test vc2.value == y.value MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) obj = MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), MOI.ScalarQuadraticTerm.([4.0, 2.0, 1.0], [x, y, x], [x, y, y]), + MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 1.0 ) MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj) @@ -470,7 +491,7 @@ function qp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function qcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qcp1test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # quadratic constraint @@ -495,23 +516,33 @@ function qcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # begin new for w in [x, y] - lb = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.GreaterThan(-3.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(3.0)) + lb = MOI.add_constraint(model, w, MOI.GreaterThan(-3.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(3.0)) @test ub.value == w.value @test lb.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 2 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 2 # end new - c1 = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,1,2,2], MOI.ScalarAffineTerm.([-1.0,1.0,1.0,1.0], [x,y,x,y])), [0.0,0.0]), MOI.Nonnegatives(2)) + c1 = MOI.add_constraint(model, + MOI.VectorAffineFunction( + MOI.VectorAffineTerm.( + [1,1,2,2], + MOI.ScalarAffineTerm.([-1.0,1.0,1.0,1.0], [x,y,x,y])), + [0.0,0.0]), + MOI.Nonnegatives(2)) @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()) == 1 - c2f = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(1.0, y)], [MOI.ScalarQuadraticTerm(2.0, x, x)], 0.0) + c2f = MOI.ScalarQuadraticFunction( + [MOI.ScalarQuadraticTerm(2.0, x, x)], + [MOI.ScalarAffineTerm(1.0, y)], + 0.0) c2 = MOI.add_constraint(model, c2f, MOI.LessThan(2.0)) @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [x,y]), 0.0)) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [x,y]), 0.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE @@ -561,7 +592,7 @@ function qcp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0.0 atol=atol rtol=rtol end -function qcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qcp2test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Max x @@ -579,20 +610,24 @@ function qcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # begin new for w in [x] - lb = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.GreaterThan(-3.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(3.0)) + lb = MOI.add_constraint(model, w, MOI.GreaterThan(-3.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(3.0)) @test ub.value == w.value @test lb.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 1 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 1 # end new - cf = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], [MOI.ScalarQuadraticTerm(2.0, x, x)], 0.0) + cf = MOI.ScalarQuadraticFunction( + [MOI.ScalarQuadraticTerm(2.0, x, x)], + [MOI.ScalarAffineTerm(0.0, x)], + 0.0) c = MOI.add_constraint(model, cf, MOI.LessThan(2.0)) @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE @@ -625,7 +660,7 @@ function qcp2test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function qcp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function qcp3test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # Min -x @@ -643,20 +678,25 @@ function qcp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # begin new for w in [x] - lb = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.GreaterThan(-3.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(3.0)) + lb = MOI.add_constraint(model, w, MOI.GreaterThan(-3.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(3.0)) @test ub.value == w.value @test lb.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 1 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 1 # end new - cf = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[], [MOI.ScalarQuadraticTerm(2.0, x, x)], 0.0) + cf = MOI.ScalarQuadraticFunction( + [MOI.ScalarQuadraticTerm(2.0, x, x)], + MOI.ScalarAffineTerm{Float64}[], + 0.0) c = MOI.add_constraint(model, cf, MOI.LessThan(2.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x)], 0.0)) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x)], 0.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE @@ -693,7 +733,7 @@ function qcp3test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) end end -function _qcp4test_mod(model::MOI.ModelLike, config::MOIT.TestConfig, less_than::Bool) +function _qcp4test_mod(model::MOI.ModelLike, config::MOIT.Config, less_than::Bool) atol = config.atol rtol = config.rtol # Max x @@ -712,27 +752,30 @@ function _qcp4test_mod(model::MOI.ModelLike, config::MOIT.TestConfig, less_than: y = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - vc = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.EqualTo(1.0)) + vc = MOI.add_constraint(model, y, MOI.EqualTo(1.0)) @test vc.value == y.value # begin new for w in [x] - lb = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.GreaterThan(-3.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(3.0)) + lb = MOI.add_constraint(model, w, MOI.GreaterThan(-3.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(3.0)) @test ub.value == w.value @test lb.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 1 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 1 # end new - cf = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], - MOI.ScalarQuadraticTerm.([2.0, 1.0, 2.0], [x, x, y], [x, y, y]), 0.0) + cf = MOI.ScalarQuadraticFunction( + MOI.ScalarQuadraticTerm.([2.0, 1.0, 2.0], [x, x, y], [x, y, y]), + [MOI.ScalarAffineTerm(0.0, x)], + 0.0) if !less_than MOIU.operate!(-, Float64, cf) end c = MOI.add_constraint(model, cf, quad_set) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, typeof(quad_set)}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, typeof(quad_set)}()) == 1 MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)) @@ -774,10 +817,10 @@ function _qcp4test_mod(model::MOI.ModelLike, config::MOIT.TestConfig, less_than: end end -qcp4test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) = _qcp4test_mod(model, config, true) -qcp5test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) = _qcp4test_mod(model, config, false) +qcp4test_mod(model::MOI.ModelLike, config::MOIT.Config) = _qcp4test_mod(model, config, true) +qcp5test_mod(model::MOI.ModelLike, config::MOIT.Config) = _qcp4test_mod(model, config, false) -function socp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) +function socp1test_mod(model::MOI.ModelLike, config::MOIT.Config) atol = config.atol rtol = config.rtol # min t @@ -789,7 +832,7 @@ function socp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{Float64}) + @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.GreaterThan{Float64}) MOI.empty!(model) @test MOI.is_empty(model) @@ -801,33 +844,36 @@ function socp1test_mod(model::MOI.ModelLike, config::MOIT.TestConfig) # begin new for w in [x, y] - lb = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.GreaterThan(-3.0)) - ub = MOI.add_constraint(model, MOI.SingleVariable(w), MOI.LessThan(3.0)) + lb = MOI.add_constraint(model, w, MOI.GreaterThan(-3.0)) + ub = MOI.add_constraint(model, w, MOI.LessThan(3.0)) @test ub.value == w.value @test lb.value == w.value end - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 2 - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 2 - MOI.add_constraint(model, MOI.SingleVariable(t), MOI.LessThan(3.0)) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.LessThan{Float64}}()) == 2 + MOI.add_constraint(model, t, MOI.LessThan(3.0)) # end new c1f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x,y]), 0.0) c1 = MOI.add_constraint(model, c1f, MOI.GreaterThan(1.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) == 1 c2f = MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm{Float64}[], MOI.ScalarQuadraticTerm.([2.0, 2.0, -2.0], [x, y, t], [x, y, t]), + MOI.ScalarAffineTerm{Float64}[], 0.0 ) c2 = MOI.add_constraint(model, c2f, MOI.LessThan(0.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 + @test MOI.get(model, + MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 - bound = MOI.add_constraint(model, MOI.SingleVariable(t), MOI.GreaterThan(0.0)) + bound = MOI.add_constraint(model, t, MOI.GreaterThan(0.0)) @test bound.value == t.value - @test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == 3 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VariableIndex, MOI.GreaterThan{Float64}}()) == 3 - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, t)], 0.0)) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, t)], 0.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE diff --git a/test/runtests.jl b/test/runtests.jl index 9c63144..f4e6a21 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,7 @@ using QuadraticToBinary, Test, MathOptInterface const MOI = MathOptInterface const MOIU = MOI.Utilities -const MOIT = MOI.Test +const MOIT = MOI.DeprecatedTest const MOIB = MOI.Bridges optimizers = [] @@ -11,9 +11,129 @@ include("solvers/cbc.jl") include("moi.jl") -const CONFIG_LOW_TOL = MOIT.TestConfig(atol = 1e-3, rtol = 1e-2, duals = false, infeas_certificates = false) -const CONFIG_VERY_LOW_TOL = MOIT.TestConfig(atol = 5e-3, rtol = 5e-2, duals = false, infeas_certificates = false) -const CONFIG = MOIT.TestConfig(duals = false, infeas_certificates = false) +const CONFIG_LOW_TOL = MOIT.Config{Float64}(atol = 1e-3, rtol = 1e-2, duals = false, infeas_certificates = false) +const CONFIG_VERY_LOW_TOL = MOIT.Config{Float64}(atol = 5e-3, rtol = 5e-2, duals = false, infeas_certificates = false) +const CONFIG = MOIT.Config{Float64}(duals = false, infeas_certificates = false) + +function test_runtests() + optimizer = MOI.instantiate(Cbc.Optimizer, with_bridge_type = Float64) + model = QuadraticToBinary.Optimizer{Float64}(optimizer) + MOI.set(model, MOI.Silent(), true) + MOI.Test.runtests( + model, + MOI.Test.Config( + atol = 1e-3, rtol = 1e-2, + exclude = Any[ + # MOI.DualStatus, + # Cbc limitations + MOI.ConstraintDual, + MOI.DualObjectiveValue, + MOI.ConstraintBasisStatus, + MOI.VariableBasisStatus, + ] + ), + exclude = String[ + # require bounds on variables: + "test_quadratic_nonhomogeneous", + "test_quadratic_nonconvex_constraint_integration", + "test_quadratic_nonconvex_constraint_basic", + "test_quadratic_integration", + "test_quadratic_duplicate_terms", + "test_quadratic_constraint_minimize", + "test_quadratic_constraint_integration", + "test_quadratic_constraint_basic", + "test_quadratic_constraint_LessThan", + "test_quadratic_constraint_GreaterThan", + "test_quadratic_SecondOrderCone_basic", + "test_objective_qp_ObjectiveFunction_zero_ofdiag", + "test_objective_qp_ObjectiveFunction_edge_cases", + "test_constraint_qcp_duplicate_off_diagonal", + "test_constraint_qcp_duplicate_diagonal", + # TODO: broken in Cbc: + # https://github.com/jump-dev/Cbc.jl/blob/master/test/MOI_wrapper.jl + # TODO(odow): bug in Cbc.jl + "test_model_copy_to_UnsupportedAttribute", + "test_model_ModelFilter_AbstractConstraintAttribute", + # TODO(odow): bug in MOI + "test_model_LowerBoundAlreadySet", + "test_model_UpperBoundAlreadySet", + # TODO(odow): upstream bug in Cbc + "_Indicator_", + "test_linear_SOS1_integration", + "test_linear_SOS2_integration", + "test_solve_SOS2_add_and_delete", + # Can't prove infeasible. + "test_conic_NormInfinityCone_INFEASIBLE", + "test_conic_NormOneCone_INFEASIBLE", + "test_solve_TerminationStatus_DUAL_INFEASIBLE", + ], + ) + #= + Hard + =# + optimizer = MOI.instantiate(Cbc.Optimizer, with_bridge_type = Float64) + model = QuadraticToBinary.Optimizer{Float64}(optimizer) + MOI.set(model, QuadraticToBinary.FallbackUpperBound(), +10.0) + MOI.set(model, QuadraticToBinary.FallbackLowerBound(), -10.0) + MOI.set(model, QuadraticToBinary.GlobalVariablePrecision(), 1e-5) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.TimeLimitSec(), 20.0) + MOI.Test.runtests( + model, + MOI.Test.Config( + atol = 5e-3, + rtol = 5e-2, + exclude = Any[ + MOI.ConstraintDual, + MOI.DualObjectiveValue + ], + ), + include = String[ + "test_quadratic_nonhomogeneous", + "test_quadratic_nonconvex_constraint_integration", + "test_quadratic_nonconvex_constraint_basic", + "test_quadratic_constraint_minimize", + "test_quadratic_constraint_integration", + "test_quadratic_constraint_basic", + "test_quadratic_constraint_LessThan", + "test_quadratic_constraint_GreaterThan", + "test_objective_qp_ObjectiveFunction_zero_ofdiag", + "test_objective_qp_ObjectiveFunction_edge_cases", + "test_constraint_qcp_duplicate_off_diagonal", + "test_constraint_qcp_duplicate_diagonal", + ] + ) + #= + more precise bounds + =# + MOI.set(model, QuadraticToBinary.FallbackUpperBound(), 1.0) + MOI.set(model, QuadraticToBinary.FallbackLowerBound(), 0.0) + MOI.set(model, QuadraticToBinary.GlobalVariablePrecision(), 1e-5) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.TimeLimitSec(), 80.0) + MOI.Test.runtests( + model, + MOI.Test.Config( + atol = 5e-3, + rtol = 5e-2, + optimal_status = MOI.OPTIMAL, + exclude = Any[ + MOI.ConstraintDual, + MOI.DualObjectiveValue + ], + ), + include = String[ + "test_quadratic_SecondOrderCone_basic", + "test_quadratic_integration", + "test_quadratic_duplicate_terms", + ] + ) + + return +end +@testset "MOI Unit" begin + @time test_runtests() +end @testset "basic_constraint_tests" begin for opt in optimizers @@ -22,7 +142,45 @@ const CONFIG = MOIT.TestConfig(duals = false, infeas_certificates = false) end end -@testset "Unit" begin +@testset "readme" begin + for opt in optimizers + + MOI.empty!(opt) + @test MOI.is_empty(opt) + + model = QuadraticToBinary.Optimizer{Float64}(opt) + + x = MOI.add_variable(model) + y = MOI.add_variable(model) + + MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + MOI.add_constraint(model, y, MOI.GreaterThan(1.0)) + + MOI.add_constraint(model, x, MOI.LessThan(10.0)) + MOI.add_constraint(model, y, MOI.LessThan(10.0)) + + c = MOI.add_constraint(model, 1.0 * x * y, MOI.LessThan(4.0)) + + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + 2.0 * x + y) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + + MOI.optimize!(model) + + @assert MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + + @assert MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + + @assert MOI.get(model, MOI.ObjectiveValue()) ≈ 9.0 + + @assert MOI.get(model, MOI.VariablePrimal(), x) ≈ 4.0 + @assert MOI.get(model, MOI.VariablePrimal(), y) ≈ 1.0 + + @assert MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 4.0 + end +end + +@testset "MOI Deprecated Unit" begin for opt in optimizers MODEL = QuadraticToBinary.Optimizer{Float64}(opt) MOIT.unittest(MODEL, CONFIG, [ @@ -120,4 +278,4 @@ end qp2test_mod(MODEL, CONFIG_VERY_LOW_TOL) qp3test_mod(MODEL, CONFIG_LOW_TOL) end -end \ No newline at end of file +end