diff --git a/docs/src/manual.md b/docs/src/manual.md index d8ff2e1e..41f8ce6f 100644 --- a/docs/src/manual.md +++ b/docs/src/manual.md @@ -14,7 +14,7 @@ A main concern is to efficiently implement this new type, as one typical usage i ## How it works -The main idea applied in POI is that the interaction between the solver, e.g. `GLPK`, and the optimization model will be handled by `MOI` as usual. Because of that, `POI` is a higher level wrapper around `MOI`, responsible for receiving variables, constants and parameters, and forwarding to the lower level model only variables and constants. +The main idea applied in POI is that the interaction between the solver, e.g. `HiGHS`, and the optimization model will be handled by `MOI` as usual. Because of that, `POI` is a higher level wrapper around `MOI`, responsible for receiving variables, constants and parameters, and forwarding to the lower level model only variables and constants. As `POI` receives parameters, it must analyze and decide how they should be handled on the lower level optimization model (the `MOI` model). diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 70f98805..b9a0ff4c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -3,11 +3,11 @@ # function _is_variable(v::MOI.VariableIndex) - return v.value < PARAMETER_INDEX_THRESHOLD + return !_is_parameter(v) end function _is_parameter(v::MOI.VariableIndex) - return v.value > PARAMETER_INDEX_THRESHOLD + return PARAMETER_INDEX_THRESHOLD < v.value < PARAMETER_INDEX_THRESHOLD_MAX end function _has_parameters(f::MOI.ScalarAffineFunction{T}) where {T} @@ -114,8 +114,6 @@ function MOI.is_empty(model::Optimizer) isempty(model.parameters) && isempty(model.parameters_name) && isempty(model.updated_parameters) && - isempty(model.variables) && - model.last_variable_index_added == 0 && model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && isempty(model.constraint_outer_to_inner) && # affine ctr @@ -153,8 +151,6 @@ function MOI.empty!(model::Optimizer{T}) where {T} empty!(model.parameters) empty!(model.parameters_name) empty!(model.updated_parameters) - empty!(model.variables) - model.last_variable_index_added = 0 model.last_parameter_index_added = PARAMETER_INDEX_THRESHOLD empty!(model.constraint_outer_to_inner) # affine ctr @@ -194,9 +190,9 @@ end # function MOI.is_valid(model::Optimizer, vi::MOI.VariableIndex) - if haskey(model.variables, vi) + if haskey(model.parameters, p_idx(vi)) return true - elseif haskey(model.parameters, p_idx(vi)) + elseif MOI.is_valid(model.optimizer, vi) return true end return false @@ -248,8 +244,12 @@ function MOI.get(model::Optimizer, tp::Type{MOI.VariableIndex}, attr::String) end function _add_variable(model::Optimizer, inner_vi) - _next_variable_index!(model) - return MOI.Utilities.CleverDicts.add_item(model.variables, inner_vi) + if _is_parameter(inner_vi) + error( + "Attempted to add a variable but got a parameter index. The inner solver should not create variables with index >= $PARAMETER_INDEX_THRESHOLD, (got $(inner_vi.value)).", + ) + end + return inner_vi end function MOI.add_variable(model::Optimizer) @@ -368,15 +368,15 @@ function MOI.get( attr::MOI.VariablePrimalStart, v::MOI.VariableIndex, ) - if _variable_in_model(model, v) - return MOI.get(model.optimizer, attr, v) - elseif _parameter_in_model(model, v) - # this is effectivelly a no-op, but we do validation - _val = model.parameters[p_idx(v)] - return _val - else - error("Variable not in the model") + if _is_parameter(v) + if haskey(model.parameters, p_idx(v)) + return model.parameters[p_idx(v)] + else + throw(MOI.InvalidIndex(v)) + end end + # inner model will throw if not valid + return MOI.get(model.optimizer, attr, v) end function MOI.set( @@ -385,9 +385,10 @@ function MOI.set( v::MOI.VariableIndex, val, ) - if _variable_in_model(model, v) - MOI.set(model.optimizer, attr, v, val) - elseif _parameter_in_model(model, v) + if _is_parameter(v) + if !haskey(model.parameters, p_idx(v)) + throw(MOI.InvalidIndex(v)) + end # this is effectivelly a no-op, but we do validation _val = model.parameters[p_idx(v)] if val != _val @@ -395,10 +396,9 @@ function MOI.set( "The parameter $v value is $_val, but trying to set VariablePrimalStart $val", ) end - else - error("Variable not in the model") + return end - return + return MOI.set(model.optimizer, attr, v, val) end function MOI.set( @@ -407,11 +407,10 @@ function MOI.set( v::MOI.VariableIndex, val, ) - if _variable_in_model(model, v) - MOI.set(model.optimizer, attr, v, val) - else - error("$attr is not supported for parameters") + if _is_parameter(v) + error("$attr is not supported for parameters in ParametricOptInterface") end + return MOI.set(model.optimizer, attr, v, val) end function MOI.get( @@ -419,15 +418,19 @@ function MOI.get( attr::MOI.AbstractVariableAttribute, v::MOI.VariableIndex, ) - if _variable_in_model(model, v) - return MOI.get(model.optimizer, attr, model.variables[v]) - else - error("$attr is not supported for parameters") + if _is_parameter(v) + error("$attr is not supported for parameters in ParametricOptInterface") end + return MOI.get(model.optimizer, attr, v) end function MOI.delete(model::Optimizer, v::MOI.VariableIndex) - delete!(model.variables, v) + if !MOI.is_valid(model, v) + throw(MOI.InvalidIndex(v)) + end + if _is_parameter(v) + error("Cannot delete parameters in ParametricOptInterface.") + end MOI.delete(model.optimizer, v) MOI.delete(model.original_objective_cache, v) # TODO - what happens if the variable was in a SAF that was converted to bounds? @@ -437,6 +440,7 @@ function MOI.delete(model::Optimizer, v::MOI.VariableIndex) model.constraint_outer_to_inner, ) _delete_variable_index_constraint( + model, model.constraint_outer_to_inner, F, S, @@ -446,11 +450,28 @@ function MOI.delete(model::Optimizer, v::MOI.VariableIndex) return end -function _delete_variable_index_constraint(d, F, S, v) +function _delete_variable_index_constraint(model, d, F, S, v) + return +end + +function _delete_variable_index_constraint( + model, + d, + F::Type{MOI.VectorOfVariables}, + S, + v, +) + inner = d[F, S] + for (key, val) in inner + if !MOI.is_valid(model.optimizer, val) + delete!(inner, key) + end + end return end function _delete_variable_index_constraint( + model, d, F::Type{MOI.VariableIndex}, S, @@ -714,7 +735,7 @@ function MOI.get( ) where {T} p = MOI.VariableIndex(cp.value) if !_parameter_in_model(model, p) - error("Parameter not in the model") + throw(MOI.InvalidIndex(cp)) end return p end @@ -758,7 +779,7 @@ function MOI.set( _assert_parameter_is_finite(set) p = MOI.VariableIndex(cp.value) if !_parameter_in_model(model, p) - error("Parameter not in the model") + throw(MOI.InvalidIndex(cp)) end return model.updated_parameters[p_idx(p)] = set.value end @@ -770,7 +791,7 @@ function MOI.get( ) where {T} p = MOI.VariableIndex(cp.value) if !_parameter_in_model(model, p) - error("Parameter not in the model") + throw(MOI.InvalidIndex(cp)) end val = model.updated_parameters[p_idx(p)] if isnan(val) @@ -786,7 +807,9 @@ function MOI.modify( ) where {F,S,T} if haskey(model.quadratic_constraint_cache, c) || haskey(model.affine_constraint_cache, c) - error("Parametric constraint cannot be modified") + error( + "Parametric constraint cannot be modified in ParametricOptInterface, because it would conflict with parameter updates. You can update the parameters instead.", + ) end MOI.modify(model.optimizer, c, chg) return @@ -803,10 +826,10 @@ function MOI.add_constraint( f::MOI.VariableIndex, set::MOI.AbstractScalarSet, ) - if !_is_variable(f) - error("Cannot constrain a parameter") - elseif !_variable_in_model(model, f) - error("Variable not in the model") + if _is_parameter(f) + error("Cannot constrain a parameter in ParametricOptInterface.") + elseif !MOI.is_valid(model, f) + throw(MOI.InvalidIndex(f)) end return _add_constraint_direct_and_cache_map!(model, f, set) end @@ -822,7 +845,7 @@ function _add_constraint_with_parameters_on_function( poi_ci = _add_vi_constraint(model, pf, set) else error( - "It was not possible to interpret this constraint as a variable bound.", + "It was not possible to interpret this constraint as a variable bound. You can change the `ConstraintsInterpretation` to BOUNDS_AND_CONSTRAINTS or ONLY_CONSTRAINTS to allow this constraint to be added as a general constraint.", ) end elseif model.constraints_interpretation == ONLY_CONSTRAINTS @@ -901,7 +924,9 @@ function MOI.add_constraint( set::MOI.AbstractVectorSet, ) if _has_parameters(f) - error("VectorOfVariables does not allow parameters") + error( + "VectorOfVariables does not allow parameters in ParametricOptInterface.", + ) end return _add_constraint_direct_and_cache_map!(model, f, set) end @@ -1073,6 +1098,9 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.VectorQuadraticFunction,S<:MOI.AbstractSet} + if !MOI.is_valid(model, c) + throw(MOI.InvalidIndex(c)) + end c_aux = c if haskey(model.vector_quadratic_outer_to_inner, c) ci_inner = model.vector_quadratic_outer_to_inner[c] @@ -1090,6 +1118,9 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.ScalarQuadraticFunction,S<:MOI.AbstractSet} + if !MOI.is_valid(model, c) + throw(MOI.InvalidIndex(c)) + end c_aux = c if haskey(model.quadratic_outer_to_inner, c) ci_inner = model.quadratic_outer_to_inner[c] @@ -1107,6 +1138,9 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.ScalarAffineFunction,S<:MOI.AbstractSet} + if !MOI.is_valid(model, c) + throw(MOI.InvalidIndex(c)) + end if haskey(model.affine_outer_to_inner, c) ci_inner = model.affine_outer_to_inner[c] delete!(model.affine_outer_to_inner, c) @@ -1124,6 +1158,9 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:Union{MOI.VariableIndex,MOI.VectorOfVariables},S<:MOI.AbstractSet} + if !MOI.is_valid(model, c) + throw(MOI.InvalidIndex(c)) + end MOI.delete(model.optimizer, c) delete!(model.constraint_outer_to_inner, c) return @@ -1133,6 +1170,9 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.VectorAffineFunction,S<:MOI.AbstractSet} + if !MOI.is_valid(model, c) + throw(MOI.InvalidIndex(c)) + end ci_inner = model.constraint_outer_to_inner[c] if haskey(model.vector_affine_constraint_cache, ci_inner) delete!(model.vector_affine_constraint_cache, ci_inner) @@ -1148,6 +1188,13 @@ end # Objective # +function MOI.supports( + model::Optimizer, + attr::MOI.ObjectiveFunction{T}, +) where {T} + return false +end + function MOI.supports( model::Optimizer, attr::Union{ @@ -1177,7 +1224,9 @@ function MOI.modify( if model.quadratic_objective_cache !== nothing || model.affine_objective_cache !== nothing || !isempty(model.quadratic_objective_cache_product) - error("Parametric objective cannot be modified") + error( + "A parametric objective cannot be modified as it would conflict with the parameter update mechanism. Please set a new objective or use parameters to perform such updates.", + ) end MOI.modify(model.optimizer, c, chg) MOI.modify(model.original_objective_cache, c, chg) @@ -1261,11 +1310,14 @@ function MOI.set( v::MOI.VariableIndex, ) if _is_parameter(v) - error("Cannot use a parameter as objective function alone") - elseif !_variable_in_model(model, v) - error("Variable not in the model") + # TODO + error( + "Cannot use a parameter as objective function alone in ParametricOptInterface.", + ) + elseif !MOI.is_valid(model, v) + throw(MOI.InvalidIndex(v)) end - MOI.set(model.optimizer, attr, model.variables[v]) + MOI.set(model.optimizer, attr, v) MOI.set(model.original_objective_cache, attr, v) return end @@ -1313,9 +1365,13 @@ function MOI.get( model::Optimizer, ::MOI.ListOfConstraintAttributesSet{F,S}, ) where {F,S} - if F === MOI.ScalarQuadraticFunction + if F <: MOI.ScalarQuadraticFunction error( - "MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction.", + "MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.", + ) + elseif F <: MOI.VectorQuadraticFunction + error( + "MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.", ) end return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}()) @@ -1327,7 +1383,7 @@ function MOI.get(model::Optimizer, ::MOI.NumberOfVariables) end function MOI.get(model::Optimizer, ::MOI.NumberOfConstraints{F,S}) where {S,F} - return length(model.constraint_outer_to_inner[F, S]) + return Int64(length(model.constraint_outer_to_inner[F, S])) end function MOI.get(model::Optimizer, ::MOI.ListOfVariableIndices) @@ -1390,12 +1446,10 @@ end function MOI.set(::Optimizer, attr::MOI.NLPBlock, val) throw(MOI.UnsupportedAttribute(attr)) - return end function MOI.get(::Optimizer, attr::MOI.NLPBlock) throw(MOI.UnsupportedAttribute(attr)) - return end function MOI.supports(model::Optimizer, attr::MOI.AbstractModelAttribute) @@ -1415,13 +1469,14 @@ function MOI.get( attr::MOI.VariablePrimal, v::MOI.VariableIndex, ) - if _parameter_in_model(model, v) - return model.parameters[p_idx(v)] - elseif _variable_in_model(model, v) - return MOI.get(model.optimizer, attr, model.variables[v]) - else - error("Variable not in the model") + if _is_parameter(v) + if haskey(model.parameters, p_idx(v)) + return model.parameters[p_idx(v)] + else + throw(MOI.InvalidIndex(v)) + end end + return MOI.get(model.optimizer, attr, v) end function MOI.get( @@ -1476,7 +1531,7 @@ function MOI.set( ) optimizer_ci = get(model.constraint_outer_to_inner, c, nothing) if optimizer_ci === nothing - error("Constraint $c not in the model") + throw(MOI.InvalidIndex(c)) end return MOI.set(model.optimizer, attr, optimizer_ci, val) end @@ -1487,7 +1542,9 @@ function MOI.set( c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}}, val, ) where {T} - return error("Constraint attribute $attr cannot be set for $c") + return error( + "Constraint attribute $attr cannot be set for $c in ParametricOptInterface.", + ) end function MOI.get( @@ -1508,7 +1565,7 @@ function MOI.get( if _parameter_in_model(model, v) return model.parameters[p_idx(v)] else - error("Variable not in the model") + throw(MOI.InvalidIndex(c)) end end @@ -1527,7 +1584,7 @@ function MOI.set( ) end else - error("Variable not in the model") + throw(MOI.InvalidIndex(c)) end return end @@ -1540,7 +1597,7 @@ function MOI.set( ) where {T} v = MOI.VariableIndex(c.value) if !_parameter_in_model(model, v) - error("Variable not in the model") + throw(MOI.InvalidIndex(c)) end return end @@ -1681,7 +1738,7 @@ A model attribute for the number of pure variables in the model. struct NumberOfPureVariables <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::NumberOfPureVariables) - return length(model.variables) + return MOI.get(model.optimizer, MOI.NumberOfVariables()) end """ @@ -1692,7 +1749,7 @@ A model attribute for the list of pure variable indices in the model. struct ListOfPureVariableIndices <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::ListOfPureVariableIndices) - return collect(keys(model.variables))::Vector{MOI.VariableIndex} + return MOI.get(model.optimizer, MOI.ListOfVariableIndices()) end """ diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 32710300..4c08b86d 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -18,6 +18,7 @@ const MOI = MathOptInterface const SIMPLE_SCALAR_SETS{T} = Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}} +const PARAMETER_INDEX_THRESHOLD_MAX = typemax(Int64) # div(typemax(Int64),2)+1 const PARAMETER_INDEX_THRESHOLD = Int64(4_611_686_018_427_387_904) # div(typemax(Int64),2)+1 struct ParameterIndex @@ -100,8 +101,8 @@ optimization model. ## Example ```julia-repl -julia> ParametricOptInterface.Optimizer(GLPK.Optimizer()) -ParametricOptInterface.Optimizer{Float64,GLPK.Optimizer} +julia> ParametricOptInterface.Optimizer(HiGHS.Optimizer()) +ParametricOptInterface.Optimizer{Float64,HiGHS.Optimizer} ``` """ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer @@ -249,26 +250,23 @@ end Optimizer(args...; kws...) = Optimizer{Float64}(args...; kws...) -function _next_variable_index!(model::Optimizer) - return model.last_variable_index_added += 1 -end - function _next_parameter_index!(model::Optimizer) return model.last_parameter_index_added += 1 end +# TODO: remove this function _update_number_of_parameters!(model::Optimizer) return model.number_of_parameters_in_model += 1 end function _parameter_in_model(model::Optimizer, v::MOI.VariableIndex) - return PARAMETER_INDEX_THRESHOLD < - v.value <= - model.last_parameter_index_added -end - -function _variable_in_model(model::Optimizer, v::MOI.VariableIndex) - return 0 < v.value <= model.last_variable_index_added + if !_is_parameter(v) + return false + elseif haskey(model.parameters, p_idx(v)) + return true + else + return false + end end include("duals.jl") diff --git a/test/Project.toml b/test/Project.toml index d36efb6a..503064b5 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,6 @@ [deps] GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -9,6 +10,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] GLPK = "1" +HiGHS = "1" Ipopt = "1" JuMP = "1" SCS = "1" diff --git a/test/jump_tests.jl b/test/jump_tests.jl index ffaf212a..4a5bb004 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1535,7 +1535,7 @@ function test_jump_psd_cone_without_parameter_v_and_vv() [x, p * (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) ) @test_throws ErrorException( - "Constraint attribute MathOptInterface.ConstraintName() cannot be set for $(index(ParameterRef(p)))", + "Constraint attribute MathOptInterface.ConstraintName() cannot be set for $(index(ParameterRef(p))) in ParametricOptInterface.", ) MOI.set( backend(model), MOI.ConstraintName(), @@ -1555,62 +1555,123 @@ function test_variable_and_constraint_not_registered() SCS.Optimizer(), ) optimizer1 = POI.Optimizer(cached1) + model1 = direct_model(optimizer1) cached2 = MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), SCS.Optimizer(), ) optimizer2 = POI.Optimizer(cached2) - model = direct_model(optimizer1) model2 = direct_model(optimizer2) - set_silent(model) + set_silent(model1) set_silent(model2) - @variable(model, x) - @variable(model, p in MOI.Parameter(1.0)) - @variable(model, p1 in MOI.Parameter(1.0)) + @variable(model1, x) + @variable(model1, p in MOI.Parameter(1.0)) + @variable(model1, p1 in MOI.Parameter(1.0)) @variable(model2, p2 in MOI.Parameter(1.0)) - @constraint(model, con, [x - p] in MOI.Nonnegatives(1)) - @test_throws ErrorException("Variable not in the model") MOI.set( - backend(model2), - MOI.VariablePrimalStart(), - index(x), - 1.0, - ) - @test_throws ErrorException("Variable not in the model") MOI.get( - backend(model2), - MOI.VariablePrimalStart(), - index(x), - ) - @test_throws ErrorException("Parameter not in the model") MOI.get( + @constraint(model1, con, [x - p] in MOI.Nonnegatives(1)) + @test !MOI.is_valid(backend(model2), index(x)) + @test_throws MOI.InvalidIndex MOI.get( backend(model2), MOI.ConstraintFunction(), index(ParameterRef(p1)), ) - @test_throws ErrorException("Parameter not in the model") MOI.get( + @test_throws MOI.InvalidIndex MOI.get( backend(model2), MOI.ConstraintSet(), index(ParameterRef(p1)), ) - @test_throws ErrorException("Variable not in the model") MOI.set( + @test_throws MOI.InvalidIndex MOI.set( backend(model2), MOI.ConstraintPrimalStart(), index(ParameterRef(p1)), 1.0, ) - @test_throws ErrorException("Variable not in the model") MOI.get( + @test_throws MOI.InvalidIndex MOI.get( backend(model2), MOI.ConstraintPrimalStart(), index(ParameterRef(p1)), ) - @test_throws ErrorException("Variable not in the model") MOI.set( + @test_throws MOI.InvalidIndex MOI.set( backend(model2), MOI.ObjectiveFunction{MOI.VariableIndex}(), index(x), ) @test_throws ErrorException( - "Cannot use a parameter as objective function alone", + "Cannot use a parameter as objective function alone in ParametricOptInterface.", ) MOI.set( backend(model2), MOI.ObjectiveFunction{MOI.VariableIndex}(), index(p), ) + MOI.set(backend(model2), MOI.VariablePrimalStart(), index(p2), 1.0) + @test_throws ErrorException MOI.set( + backend(model2), + MOI.VariablePrimalStart(), + index(p2), + 10.0, + ) + @test_throws MOI.InvalidIndex MOI.set( + backend(model2), + MOI.VariablePrimalStart(), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 100), + 10.0, + ) + @test_throws MOI.InvalidIndex MOI.get( + backend(model2), + MOI.VariablePrimalStart(), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 100), + ) + @test_throws ErrorException MOI.delete(backend(model2), index(p2)) + @test_throws MOI.InvalidIndex MOI.delete( + backend(model2), + MOI.ConstraintIndex{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonpositives, + }( + 1, + ), + ) + @test_throws MOI.InvalidIndex MOI.delete( + backend(model2), + MOI.ConstraintIndex{ + MOI.ScalarQuadraticFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ), + ) + @test_throws MOI.InvalidIndex MOI.get( + backend(model2), + MOI.VariablePrimal(), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 100), + ) + return +end + +function test_jump_errors() + cached1 = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ) + optimizer1 = POI.Optimizer(cached1) + model1 = direct_model(optimizer1) + @test_throws MOI.UnsupportedAttribute MOI.get( + backend(model1), + MOI.NLPBlock(), + ) + @test_throws ErrorException MOI.get( + backend(model1), + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonpositives, + }(), + ) + @test_throws ErrorException MOI.get( + backend(model1), + MOI.ListOfConstraintAttributesSet{ + MOI.ScalarQuadraticFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + ) + return end diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 90593dc4..7bd6fc1f 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -33,12 +33,10 @@ function test_basic_tests() @test !MOI.is_valid(optimizer, z) @test MOI.is_valid(optimizer, cy) @test !MOI.is_valid(optimizer, cz) - @test_throws ErrorException("Cannot constrain a parameter") MOI.add_constraint( - optimizer, - y, - MOI.EqualTo(0.0), - ) - @test_throws ErrorException("Variable not in the model") MOI.add_constraint( + @test_throws ErrorException( + "Cannot constrain a parameter in ParametricOptInterface.", + ) MOI.add_constraint(optimizer, y, MOI.EqualTo(0.0)) + @test_throws MOI.InvalidIndex MOI.add_constraint( optimizer, z, MOI.GreaterThan(0.0), @@ -61,7 +59,7 @@ function test_basic_tests() MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.ObjectiveValue()) == 2 @test MOI.get(optimizer, MOI.VariablePrimal(), x[1]) == 2 - @test_throws ErrorException("Variable not in the model") MOI.get( + @test_throws MOI.InvalidIndex{MOI.VariableIndex}(MOI.VariableIndex(4)) MOI.get( optimizer, MOI.VariablePrimal(), z, @@ -71,7 +69,7 @@ function test_basic_tests() @test MOI.get(optimizer, POI.ListOfParameterIndices()) == POI.ParameterIndex[POI.ParameterIndex(1)] MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(1.0)) - @test_throws ErrorException("Parameter not in the model") MOI.set( + @test_throws MOI.InvalidIndex MOI.set( optimizer, MOI.ConstraintSet(), cz, @@ -116,7 +114,7 @@ function test_basic_tests() ) #err MOI.set(optimizer, MOI.ConstraintPrimalStart(), cy, 1.0) MOI.set(optimizer, MOI.ConstraintDualStart(), cy, 1.0) # no-op - @test_throws ErrorException MOI.set( + @test_throws MOI.InvalidIndex MOI.set( optimizer, MOI.ConstraintDualStart(), MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{Float64}}(18), @@ -224,19 +222,21 @@ function test_modification_multiple() return end -function test_moi_glpk() - # TODO see why tests error or fail - MOI.Test.runtests( - MOI.Bridges.full_bridge_optimizer( - POI.Optimizer(GLPK.Optimizer()), - Float64, - ), - MOI.Test.Config(); - exclude = [ - # GLPK returns INVALID_MODEL instead of INFEASIBLE - "test_constraint_ZeroOne_bounds_3", - ], +function test_moi_highs() + model = MOI.Bridges.full_bridge_optimizer( + POI.Optimizer(HiGHS.Optimizer()), + Float64, ) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off") + MOI.Test.runtests(model, MOI.Test.Config(; atol = 1e-7); exclude = []) + + model = POI.Optimizer( + MOI.instantiate(HiGHS.Optimizer; with_bridge_type = Float64), + ) + MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.RawOptimizerAttribute("presolve"), "off") + MOI.Test.runtests(model, MOI.Test.Config(; atol = 1e-7); exclude = []) return end @@ -1403,7 +1403,7 @@ function test_qp_objective_parameter_times_parameter() @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 6.0, atol = ATOL) MOI.set(optimizer, POI.ParameterValue(), y, 5) MOI.set(optimizer, POI.ParameterValue(), z, 5.0) - @test_throws ErrorException MOI.set( + @test_throws MOI.InvalidIndex MOI.set( optimizer, POI.ParameterValue(), MOI.VariableIndex(10872368175), diff --git a/test/runtests.jl b/test/runtests.jl index cbdff78f..8136738d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,7 @@ using JuMP using Test import GLPK +import HiGHS import Ipopt import SCS