diff --git a/Project.toml b/Project.toml index bb87a4ee..95e9a150 100644 --- a/Project.toml +++ b/Project.toml @@ -25,7 +25,7 @@ CodecZlib = "0.7.0" Krylov = "0.7.7" LDLFactorizations = "0.6, 0.7, 0.8" LinearOperators = "2.0" -MathOptInterface = "0.9.5" +MathOptInterface = "0.10" QPSReader = "0.2" TimerOutputs = "0.5.6" julia = "1.3" diff --git a/README.md b/README.md index 2e2c20c5..c3680bdd 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ When using Tulip through JuMP/MOI, parameters can be set either through MOI's ge ```julia moi_model = Tulip.Optimizer{Float64}() - MOI.set(moi_model, MOI.RawParameter("IPM_IterationsLimit"), 200) + MOI.set(moi_model, MOI.RawOptimizerAttribute("IPM_IterationsLimit"), 200) ``` * Through Tulip's API diff --git a/docs/src/tutorials/lp_example.md b/docs/src/tutorials/lp_example.md index 128995d5..deb0e480 100644 --- a/docs/src/tutorials/lp_example.md +++ b/docs/src/tutorials/lp_example.md @@ -88,8 +88,8 @@ x = MOI.add_variable(lp) y = MOI.add_variable(lp) # Set variable bounds -MOI.add_constraint(lp, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) # x >= 0 -MOI.add_constraint(lp, MOI.SingleVariable(y), MOI.GreaterThan(0.0)) # y >= 0 +MOI.add_constraint(lp, x, MOI.GreaterThan(0.0)) # x >= 0 +MOI.add_constraint(lp, y, MOI.GreaterThan(0.0)) # y >= 0 # Add constraints row1 = MOI.add_constraint(lp, @@ -114,7 +114,7 @@ MOI.set(lp, MOI.ObjectiveSense(), MOI.MIN_SENSE) # Set some parameters MOI.set(lp, MOI.Silent(), true) # disable output -MOI.set(lp, MOI.RawParameter("Presolve_Level"), 0) # disable presolve +MOI.set(lp, MOI.RawOptimizerAttribute("Presolve_Level"), 0) # disable presolve # Solve the problem MOI.optimize!(lp) diff --git a/examples/optimal_other_type.jl b/examples/optimal_other_type.jl index 9e7c55a1..0537d339 100644 --- a/examples/optimal_other_type.jl +++ b/examples/optimal_other_type.jl @@ -12,8 +12,8 @@ x = MOI.add_variable(lp) y = MOI.add_variable(lp) # Set variable bounds -MOI.add_constraint(lp, MOI.SingleVariable(x), MOI.GreaterThan(T(0))) # x >= 0 -MOI.add_constraint(lp, MOI.SingleVariable(y), MOI.GreaterThan(T(0))) # y >= 0 +MOI.add_constraint(lp, x, MOI.GreaterThan(T(0))) # x >= 0 +MOI.add_constraint(lp, y, MOI.GreaterThan(T(0))) # y >= 0 # Add constraints row1 = MOI.add_constraint(lp, diff --git a/src/Interfaces/MOI/MOI_wrapper.jl b/src/Interfaces/MOI/MOI_wrapper.jl index 935189a1..6e1a7ba7 100644 --- a/src/Interfaces/MOI/MOI_wrapper.jl +++ b/src/Interfaces/MOI/MOI_wrapper.jl @@ -146,6 +146,7 @@ function MOI.empty!(m::Optimizer) # Reset bound tracking m.bnd2name = Dict{MOI.ConstraintIndex, String}() m.var2bndtype = Dict{MOI.VariableIndex, Set{MOI.ConstraintIndex}}() + return nothing end function MOI.is_empty(m::Optimizer) @@ -168,10 +169,10 @@ end MOI.optimize!(m::Optimizer) = optimize!(m.inner) -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...) + return MOI.Utilities.default_copy_to(dest, src; kwargs...) end diff --git a/src/Interfaces/MOI/attributes.jl b/src/Interfaces/MOI/attributes.jl index b5193061..5fda811b 100644 --- a/src/Interfaces/MOI/attributes.jl +++ b/src/Interfaces/MOI/attributes.jl @@ -3,10 +3,10 @@ # ============================================= const SUPPORTED_OPTIMIZER_ATTR = Union{ MOI.NumberOfThreads, - MOI.RawParameter, + MOI.RawOptimizerAttribute, MOI.SolverName, MOI.Silent, - MOI.TimeLimitSec + MOI.TimeLimitSec, } MOI.supports(::Optimizer, ::A) where{A<:SUPPORTED_OPTIMIZER_ATTR} = true @@ -56,9 +56,9 @@ end # # RawParameter # -MOI.get(m::Optimizer, attr::MOI.RawParameter) = get_parameter(m.inner, attr.name) +MOI.get(m::Optimizer, attr::MOI.RawOptimizerAttribute) = get_parameter(m.inner, attr.name) -MOI.set(m::Optimizer, attr::MOI.RawParameter, val) = set_parameter(m.inner, attr.name, val) +MOI.set(m::Optimizer, attr::MOI.RawOptimizerAttribute, val) = set_parameter(m.inner, attr.name, val) # ============================================= @@ -114,7 +114,7 @@ function MOI.get( m::Optimizer{T}, ::MOI.ObjectiveFunctionType ) where{T} if m._obj_type == _SINGLE_VARIABLE - return MOI.SingleVariable + return MOI.VariableIndex else return MOI.ScalarAffineFunction{T} end @@ -148,7 +148,8 @@ end # function MOI.get(m::Optimizer{T}, attr::MOI.ObjectiveValue) where{T} MOI.check_result_index_bounds(m, attr) - return get_attribute(m.inner, ObjectiveValue()) + raw_z = get_attribute(m.inner, ObjectiveValue()) + return raw_z * !m.is_feas end # @@ -159,6 +160,8 @@ function MOI.get(m::Optimizer{T}, attr::MOI.DualObjectiveValue) where{T} return get_attribute(m.inner, DualObjectiveValue()) end +MOI.get(m::Optimizer, ::MOI.ObjectiveBound) = MOI.get(m, MOI.DualObjectiveValue()) + # # RawSolver # @@ -213,7 +216,7 @@ end # # TODO: use inner query function MOI.get(m::Optimizer, attr::MOI.PrimalStatus) - attr.N == 1 || return MOI.NO_SOLUTION + attr.result_index == 1 || return MOI.NO_SOLUTION if isnothing(m.inner.solution) return MOI.NO_SOLUTION @@ -227,7 +230,7 @@ end # # TODO: use inner query function MOI.get(m::Optimizer, attr::MOI.DualStatus) - attr.N == 1 || return MOI.NO_SOLUTION + attr.result_index == 1 || return MOI.NO_SOLUTION if isnothing(m.inner.solution) return MOI.NO_SOLUTION diff --git a/src/Interfaces/MOI/constraints.jl b/src/Interfaces/MOI/constraints.jl index 06c94717..ae974035 100644 --- a/src/Interfaces/MOI/constraints.jl +++ b/src/Interfaces/MOI/constraints.jl @@ -20,9 +20,14 @@ const SUPPORTED_CONSTR_ATTR = Union{ MOI.supports(::Optimizer, ::A, ::Type{<:MOI.ConstraintIndex}) where{A<:SUPPORTED_CONSTR_ATTR} = true +# MOI boilerplate +function MOI.supports(::Optimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex{<:MOI.VariableIndex}}) + throw(MOI.VariableIndexConstraintNameError()) +end + # Variable bounds function MOI.supports_constraint( - ::Optimizer{T}, ::Type{MOI.SingleVariable}, ::Type{S} + ::Optimizer{T}, ::Type{MOI.VariableIndex}, ::Type{S} ) where {T, S<:SCALAR_SETS{T}} return true end @@ -37,7 +42,7 @@ end function MOI.is_valid( m::Optimizer{T}, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where{T, S <:SCALAR_SETS{T}} v = MOI.VariableIndex(c.value) MOI.is_valid(m, v) || return false @@ -60,20 +65,19 @@ end # To relax variable bounds, one should delete the associated bound constraint. function MOI.add_constraint( m::Optimizer{T}, - f::MOI.SingleVariable, + v::MOI.VariableIndex, s::MOI.LessThan{T} -) where{T} +) where {T} # Check that variable exists - v = f.variable MOI.throw_if_not_valid(m, v) # Check if upper bound already exists if MOI.LessThan{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.LessThan{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.LessThan{T}}(v)) elseif MOI.EqualTo{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.LessThan{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.LessThan{T}}(v)) elseif MOI.Interval{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.LessThan{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.LessThan{T}}(v)) end # Update inner model @@ -83,25 +87,24 @@ function MOI.add_constraint( # Update bound tracking push!(m.var2bndtype[v], MOI.LessThan{T}) - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}}(v.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}}(v.value) end function MOI.add_constraint( m::Optimizer{T}, - f::MOI.SingleVariable, + v::MOI.VariableIndex, s::MOI.GreaterThan{T} ) where{T} # Check that variable exists - v = f.variable MOI.throw_if_not_valid(m, v) # Check if lower bound already exists if MOI.GreaterThan{T} ∈ m.var2bndtype[v] - throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.GreaterThan{T}}(v.value)) + throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.GreaterThan{T}}(v)) elseif MOI.EqualTo{T} ∈ m.var2bndtype[v] - throw(MOI.LowerBoundAlreadySet{MOI.EqualTo{T}, MOI.GreaterThan{T}}(v.value)) + throw(MOI.LowerBoundAlreadySet{MOI.EqualTo{T}, MOI.GreaterThan{T}}(v)) elseif MOI.Interval{T} ∈ m.var2bndtype[v] - throw(MOI.LowerBoundAlreadySet{MOI.Interval{T}, MOI.GreaterThan{T}}(v.value)) + throw(MOI.LowerBoundAlreadySet{MOI.Interval{T}, MOI.GreaterThan{T}}(v)) end # Update inner model @@ -111,27 +114,26 @@ function MOI.add_constraint( # Update upper-bound push!(m.var2bndtype[v], MOI.GreaterThan{T}) - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}}(v.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}}(v.value) end function MOI.add_constraint( m::Optimizer{T}, - f::MOI.SingleVariable, + v::MOI.VariableIndex, s::MOI.EqualTo{T} ) where{T} # Check that variable exists - v = f.variable MOI.throw_if_not_valid(m, v) # Check if a bound already exists if MOI.LessThan{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.EqualTo{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.EqualTo{T}}(v)) elseif MOI.GreaterThan{T} ∈ m.var2bndtype[v] - throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.EqualTo{T}}(v.value)) + throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.EqualTo{T}}(v)) elseif MOI.EqualTo{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.EqualTo{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.EqualTo{T}}(v)) elseif MOI.Interval{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.EqualTo{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.EqualTo{T}}(v)) end # Update inner model @@ -142,27 +144,26 @@ function MOI.add_constraint( # Update bound tracking push!(m.var2bndtype[v], MOI.EqualTo{T}) - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}(v.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.EqualTo{T}}(v.value) end function MOI.add_constraint( m::Optimizer{T}, - f::MOI.SingleVariable, + v::MOI.VariableIndex, s::MOI.Interval{T} ) where{T} # Check that variable exists - v = f.variable MOI.throw_if_not_valid(m, v) # Check if a bound already exists if MOI.LessThan{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.Interval{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.Interval{T}}(v)) elseif MOI.GreaterThan{T} ∈ m.var2bndtype[v] - throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.Interval{T}}(v.value)) + throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.Interval{T}}(v)) elseif MOI.EqualTo{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.Interval{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.Interval{T}}(v)) elseif MOI.Interval{T} ∈ m.var2bndtype[v] - throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.Interval{T}}(v.value)) + throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.Interval{T}}(v)) end # Update variable bounds @@ -173,7 +174,7 @@ function MOI.add_constraint( # Update bound tracking push!(m.var2bndtype[v], MOI.Interval{T}) - return MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}}(v.value) + return MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{T}}(v.value) end # General linear constraints @@ -196,7 +197,7 @@ function MOI.add_constraint( rval = Vector{T}(undef, nz) lb, ub = _bounds(s) for (k, t) in enumerate(fc.terms) - rind[k] = m.var_indices[t.variable_index] + rind[k] = m.var_indices[t.variable] rval[k] = t.coefficient end @@ -219,7 +220,7 @@ end # ============================================= function MOI.delete( m::Optimizer{T}, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where{T, S<:SCALAR_SETS{T}} # Sanity check @@ -304,26 +305,27 @@ end # function MOI.get( m::Optimizer{T}, - ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S} + ::MOI.ListOfConstraintIndices{MOI.VariableIndex, S} ) where{T, S<:SCALAR_SETS{T}} - indices = MOI.ConstraintIndex{MOI.SingleVariable, S}[] + indices = MOI.ConstraintIndex{MOI.VariableIndex, S}[] for (var, bounds_set) in m.var2bndtype - S ∈ bounds_set && push!(indices, MOI.ConstraintIndex{MOI.SingleVariable, S}(var.value)) + S ∈ bounds_set && push!(indices, MOI.ConstraintIndex{MOI.VariableIndex, S}(var.value)) end - return indices + return sort!(indices, by = v -> v.value) end function MOI.get( m::Optimizer{T}, ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, S} ) where{T, S<:SCALAR_SETS{T}} - return [ + indices = [ cidx for cidx in keys(m.con_indices) if isa(cidx, MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S} ) ] + return sort!(indices, by = v -> v.value) end # @@ -331,7 +333,7 @@ end # function MOI.get( m::Optimizer{T}, - ::MOI.NumberOfConstraints{MOI.SingleVariable, S} + ::MOI.NumberOfConstraints{MOI.VariableIndex, S} ) where{T, S<:SCALAR_SETS{T}} ncon = 0 for (v, bound_sets) in m.var2bndtype @@ -359,8 +361,8 @@ end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} -) where{T, S<:SCALAR_SETS{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} +) where {T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) return get(m.bnd2name, c, "") @@ -369,7 +371,7 @@ end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintName, c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S} -) where{T, S<:SCALAR_SETS{T}} +) where {T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) # Get name from inner model @@ -377,28 +379,8 @@ function MOI.get( return get_attribute(m.inner, ConstraintName(), i) end -function MOI.set( - m::Optimizer{T}, ::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.SingleVariable, S}, - name::String -) where{T, S<:SCALAR_SETS{T}} - # Sanity checks - MOI.throw_if_not_valid(m, c) - c_ = get(m.name2con, name, nothing) - c_ === nothing || c_ == c || error("Dupplicate constraint name $name") - - # Remove old name - old_name = get(m.bnd2name, c, "") - delete!(m.name2con, old_name) - - # Update new name - if name == "" - delete!(m.bnd2name, c) - else - m.bnd2name[c] = name - m.name2con[name] = c - end - return nothing +function MOI.set(::Optimizer, ::MOI.ConstraintName, ::MOI.ConstraintIndex{<:MOI.VariableIndex}, ::String) + throw(MOI.VariableIndexConstraintNameError()) end function MOI.set( @@ -410,7 +392,7 @@ function MOI.set( # Check for dupplicate name c_ = get(m.name2con, name, nothing) - c_ === nothing || c_ == c || error("Dupplicate constraint name $name") + c_ === nothing || c_ == c || error("Duplicate constraint name $name") # Update inner model i = m.con_indices[c] @@ -435,25 +417,25 @@ end # function MOI.get( m::Optimizer{T}, ::MOI.ConstraintFunction, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} -) where{T, S<:SCALAR_SETS{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} +) where {T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) # Sanity check - return MOI.SingleVariable(MOI.VariableIndex(c.value)) + return MOI.VariableIndex(c.value) end function MOI.set( - m::Optimizer{T}, ::MOI.ConstraintFunction, - c::MOI.ConstraintIndex{MOI.SingleVariable, S}, - ::MOI.SingleVariable -) where{T, S<:SCALAR_SETS{T}} - return throw(MOI.SettingSingleVariableFunctionNotAllowed()) + ::Optimizer{T}, ::MOI.ConstraintFunction, + c::MOI.ConstraintIndex{MOI.VariableIndex, S}, + ::MOI.VariableIndex, +) where {T, S<:SCALAR_SETS{T}} + return throw(MOI.SettingVariableIndexNotAllowed()) end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintFunction, c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S} -) where{T, S<:SCALAR_SETS{T}} +) where {T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) # Sanity check # Get row from inner model @@ -487,12 +469,12 @@ function MOI.set( # Set old row to zero f_old = MOI.get(m, MOI.ConstraintFunction(), c) for term in f_old.terms - j = m.var_indices[term.variable_index] + j = m.var_indices[term.variable] set_coefficient!(m.inner.pbdata, i, j, zero(T)) end # Set new row coefficients for term in fc.terms - j = m.var_indices[term.variable_index] + j = m.var_indices[term.variable] set_coefficient!(m.inner.pbdata, i, j, term.coefficient) end @@ -506,7 +488,7 @@ end # function MOI.get( m::Optimizer{T}, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}} ) where{T} # Sanity check MOI.throw_if_not_valid(m, c) @@ -521,7 +503,7 @@ end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}} ) where{T} # Sanity check MOI.throw_if_not_valid(m, c) @@ -536,7 +518,7 @@ end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.EqualTo{T}} ) where{T} # Sanity check MOI.throw_if_not_valid(m, c) @@ -551,7 +533,7 @@ end function MOI.get( m::Optimizer{T}, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}} + c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{T}} ) where{T} # Sanity check MOI.throw_if_not_valid(m, c) @@ -589,7 +571,7 @@ end function MOI.set( m::Optimizer{T}, ::MOI.ConstraintSet, - c::MOI.ConstraintIndex{MOI.SingleVariable, S}, + c::MOI.ConstraintIndex{MOI.VariableIndex, S}, s::S ) where{T, S<:SCALAR_SETS{T}} # Sanity check @@ -647,7 +629,7 @@ end # function MOI.get( m::Optimizer{T}, attr::MOI.ConstraintPrimal, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where{T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) MOI.check_result_index_bounds(m, attr) @@ -674,7 +656,7 @@ end # function MOI.get( m::Optimizer{T}, attr::MOI.ConstraintDual, - c::MOI.ConstraintIndex{MOI.SingleVariable, S} + c::MOI.ConstraintIndex{MOI.VariableIndex, S} ) where{T, S<:SCALAR_SETS{T}} MOI.throw_if_not_valid(m, c) MOI.check_result_index_bounds(m, attr) @@ -708,4 +690,4 @@ function MOI.get( else return m.inner.solution.y_lower[i] - m.inner.solution.y_upper[i] end -end \ No newline at end of file +end diff --git a/src/Interfaces/MOI/objective.jl b/src/Interfaces/MOI/objective.jl index 0e12f836..48fc354c 100644 --- a/src/Interfaces/MOI/objective.jl +++ b/src/Interfaces/MOI/objective.jl @@ -4,7 +4,7 @@ function MOI.supports( ::Optimizer{T}, ::MOI.ObjectiveFunction{F} -) where{T, F<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}}} +) where{T, F<:Union{MOI.VariableIndex, MOI.ScalarAffineFunction{T}}} return true end @@ -13,10 +13,10 @@ end # ============================================= function MOI.get( m::Optimizer{T}, - ::MOI.ObjectiveFunction{MOI.SingleVariable} + ::MOI.ObjectiveFunction{MOI.VariableIndex} ) where{T} obj = MOI.get(m, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) - return convert(MOI.SingleVariable, obj) + return convert(MOI.VariableIndex, obj) end function MOI.get( @@ -40,7 +40,7 @@ function MOI.set( m::Optimizer{T}, ::MOI.ObjectiveFunction{F}, f::F -) where{T, F <: MOI.SingleVariable} +) where{T, F <: MOI.VariableIndex} MOI.set( m, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), @@ -59,13 +59,13 @@ function MOI.set( # Sanity checks isfinite(f.constant) || error("Objective constant term must be finite") for t in f.terms - MOI.throw_if_not_valid(m, t.variable_index) + MOI.throw_if_not_valid(m, t.variable) end # Update inner model m.inner.pbdata.obj .= zero(T) # Reset inner objective to zero for t in f.terms - j = m.var_indices[t.variable_index] + j = m.var_indices[t.variable] m.inner.pbdata.obj[j] += t.coefficient # there may be dupplicates end set_attribute(m.inner, ObjectiveConstant(), f.constant) # objective offset diff --git a/src/Interfaces/MOI/variables.jl b/src/Interfaces/MOI/variables.jl index 8876bc62..c65426ea 100644 --- a/src/Interfaces/MOI/variables.jl +++ b/src/Interfaces/MOI/variables.jl @@ -98,7 +98,7 @@ function MOI.set(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex, name::S # Check that name is unique v_ = get(m.name2var, name, nothing) - v_ === nothing || v_ == v || error("Dupplicate variable name $name") + v_ === nothing || v_ == v || error("Duplicate variable name $name") # Update inner model j = m.var_indices[v] diff --git a/test/Interfaces/MOI_wrapper.jl b/test/Interfaces/MOI_wrapper.jl index efc7202a..64d16ed0 100644 --- a/test/Interfaces/MOI_wrapper.jl +++ b/test/Interfaces/MOI_wrapper.jl @@ -2,86 +2,114 @@ import MathOptInterface const MOI = MathOptInterface const MOIT = MOI.Test const MOIU = MOI.Utilities +const MOIB = MOI.Bridges const OPTIMIZER = TLP.Optimizer() MOI.set(OPTIMIZER, MOI.Silent(), true) -const CONFIG = MOIT.TestConfig(basis=false, atol=1e-6, rtol=1e-6) +const CONFIG = MOIT.Config(Float64, atol=1e-6, rtol=1e-6, exclude=Any[MOI.ConstraintBasisStatus, MOI.VariableBasisStatus]) -const MOI_EXCLUDE = [ - # Unit tests - "delete_nonnegative_variables", # Requires Vector-Of-Variables - "update_dimension_nonnegative_variables", - "delete_soc_variables", - "solve_func_vectoraffine_nonneg", # Requires `VectorAffineFunction` and `Nonnegatives` - "get_objective_function", # Require ScalarQuadratic objective - # "solve_result_index", - # "solve_singlevariable_obj", - # "solve_single_variable_dual_max", - # "solve_single_variable_dual_min", - "solve_integer_edge_cases", # Requires integer variables - "solve_qcp_edge_cases", # Requires quadratic constraints - "solve_qp_edge_cases", # Requires quadratic objective - "solve_zero_one_with_bounds_1", # Requires binary variables - "solve_zero_one_with_bounds_2", # Requires binary variables - "solve_zero_one_with_bounds_3", # Requires binary variables - "solve_affine_deletion_edge_cases", # Requires VectorAffineFunction-in-Nonpositives - "solve_objbound_edge_cases", # Requires integer variables - "solve_duplicate_terms_vector_affine", # Requires `VectorAffineFunction` - "variablenames", # TODO, requires to settle the convention on variable names - "raw_status_string", # TODO - "solve_time", # TODO - # Modifications - "delete_variable_with_single_variable_obj", # Requires SingleVariable objective function - "solve_const_vectoraffine_nonpos", # Requires VectorAffineFunction-in-Nonpositives - "solve_multirow_vectoraffine_nonpos", # Requires VectorAffineFunction-in-Nonpositives - # Linear tests - "linear7", # Requires `VectorAffineFunction` and `Nonnegatives`/`Nonpositives` - "linear15", # Requires `VectorAffineFunction` and `Zeros` - "partial_start" # Requires `VariablePrimalStart` -] +@testset "Direct optimizer" begin -@testset "MOI Unit Tests" begin - @testset "Basic constraint tests" begin - MOIT.basic_constraint_tests(OPTIMIZER, CONFIG) - end + MOIT.runtests( + OPTIMIZER, CONFIG, + exclude=[ + # already in TODO + "test_attribute_RawStatusString", + "test_attribute_SolveTimeSec", + # behaviour to implement: list of model, constraint attributes set + "test_model_ListOfConstraintAttributesSet", + "test_model_ModelFilter_AbstractModelAttribute", + "test_model_ModelFilter_ListOfConstraintIndices", + "test_model_ModelFilter_ListOfConstraintTypesPresent", + "test_model_Name", + "test_objective_set_via_modify", + # MOI expects to throw when getting duplicate cons / var names + "test_model_ScalarAffineFunction_ConstraintName", + "test_model_VariableName", + "test_model_duplicate_ScalarAffineFunction_ConstraintName", + "test_model_duplicate_VariableName", + "test_variable_VariableName", + # requires get quadratic objective + "test_objective_get_ObjectiveFunction_ScalarAffineFunction", + ] + ) - @testset "Other unit tests" begin - MOIT.unittest(OPTIMIZER, CONFIG, MOI_EXCLUDE) - end +end - @testset "Modifications" begin - MOIT.modificationtest(OPTIMIZER, CONFIG, MOI_EXCLUDE) - end +@testset "MOI Bridged" begin + BRIDGED = MOIB.full_bridge_optimizer(Tulip.Optimizer(), Float64) + MOI.set(BRIDGED, MOI.Silent(), true) + + MOIT.runtests( + BRIDGED, CONFIG, + exclude=[ + # already in TODO + "test_attribute_RawStatusString", + "test_attribute_SolveTimeSec", + # behaviour to implement: list of model, constraint attributes set + "test_conic_NormInfinityCone_3", + "test_conic_NormInfinityCone_INFEASIBLE", # should be NO_SOLUTION or INFEASIBLE_POINT + # ListOfConstraintTypePresent + "test_conic_NormInfinityCone_VectorAffineFunction", + "test_conic_NormInfinityCone_VectorOfVariables", + "test_conic_NormOneCone", + "test_conic_linear_VectorAffineFunction", + "test_conic_linear_VectorOfVariables", + "test_model_delete", + # List of attributes set + "test_model_ListOfConstraintAttributesSet", + "test_model_ModelFilter_AbstractModelAttribute", + "test_model_ModelFilter_ListOfConstraintIndices", + "test_model_ModelFilter_ListOfConstraintTypesPresent", + "test_model_Name", + "test_objective_set_via_modify", + # MOI expects to throw when getting duplicate cons / var names + "test_model_ScalarAffineFunction_ConstraintName", + "test_model_VariableName", + "test_model_duplicate_ScalarAffineFunction_ConstraintName", + "test_model_duplicate_VariableName", + "test_variable_VariableName", + # requires get quadratic objective + "test_objective_get_ObjectiveFunction_ScalarAffineFunction", + ], + ) end # Run the MOI tests with HSD and MPC algorithms for ipm in [Tulip.HSD, Tulip.MPC] @testset "MOI Linear tests - $ipm" begin OPTIMIZER.inner.params.IPM.Factory = Tulip.Factory(ipm) - MOIT.contlineartest(OPTIMIZER, CONFIG, MOI_EXCLUDE) + MOIT.runtests(OPTIMIZER, CONFIG, include=["linear"]) end end -MOI.empty!(OPTIMIZER) -const BRIDGED = MOI.Bridges.full_bridge_optimizer(OPTIMIZER, Float64) -MOI.set(BRIDGED, MOI.Silent(), true) +MOIU.@model(ModelData, + (), + (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), + (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives), + (), + (), + (MOI.ScalarAffineFunction,), + (MOI.VectorOfVariables,), + (MOI.VectorAffineFunction,) +) + +@testset "Cached optimizer" begin + CACHE = MOIU.UniversalFallback(ModelData{Float64}()) + CACHED = MOIU.CachingOptimizer(CACHE, Tulip.Optimizer()) + BRIDGED2 = MOIB.full_bridge_optimizer(CACHED, Float64) + MOI.set(BRIDGED2, MOI.Silent(), true) -@testset "Bridged tests" begin - MOIT.unittest(BRIDGED, CONFIG, [ - "get_objective_function", # Requires ScalarQuadratic objective - "delete_soc_variables", # Requires SOC constraints - "solve_integer_edge_cases", # Requires integer variables - "solve_qcp_edge_cases", # Requires quadratic constraints - "solve_qp_edge_cases", # Requires quadratic objective - "solve_zero_one_with_bounds_1", # Requires binary variables - "solve_zero_one_with_bounds_2", # Requires binary variables - "solve_zero_one_with_bounds_3", # Requires binary variables - "solve_objbound_edge_cases", # Requires integer variables - "variablenames", # TODO, requires to settle the convention on variable names - "raw_status_string", # TODO - "solve_time", # TODO - ]) - MOIT.contlineartest(BRIDGED, CONFIG, ["partial_start"]) + MOIT.runtests( + BRIDGED2, CONFIG, + exclude=[ + # already in TODO + "test_attribute_RawStatusString", + "test_attribute_SolveTimeSec", + # should be NO_SOLUTION or INFEASIBLE_POINT + "test_conic_NormInfinityCone_INFEASIBLE", + "test_conic_NormOneCone_INFEASIBLE", + ]) end diff --git a/test/Project.toml b/test/Project.toml index 24e928bd..58a80fd7 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,4 @@ [deps] -Convex = "f65535da-76fb-5f13-bab9-19810c17039a" Krylov = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" @@ -8,5 +7,4 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -Convex = "0.14" Krylov = "0.7.7" diff --git a/test/runtests.jl b/test/runtests.jl index 1521a7d6..c6bb783d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,8 +6,6 @@ using TOML using Tulip TLP = Tulip -import Convex - const TvTYPES = [Float32, Float64, BigFloat] @testset "Tulip" begin @@ -49,14 +47,14 @@ end include("Interfaces/MOI_wrapper.jl") end -@testset "Convex Problem Depot tests" begin - for T in TvTYPES - @testset "$T" begin - Convex.ProblemDepot.run_tests(; exclude=[r"mip", r"exp", r"socp", r"sdp"], T = T) do problem - Convex.solve!(problem, () -> Tulip.Optimizer{T}()) - end - end - end -end +# @testset "Convex Problem Depot tests" begin +# for T in TvTYPES +# @testset "$T" begin +# Convex.ProblemDepot.run_tests(; exclude=[r"mip", r"exp", r"socp", r"sdp"], T = T) do problem +# Convex.solve!(problem, () -> Tulip.Optimizer{T}()) +# end +# end +# end +# end end # Tulip tests