diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index f2ac5b4d7e..3936284f55 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -1153,7 +1153,7 @@ function Base.read!(io::IO, model::Model) end data = TempMPSModel() header = HEADER_NAME - while !eof(io) + while !eof(io) && header != HEADER_ENDATA raw_line = readline(io) if startswith(raw_line, '*') continue # Lines starting with `*` are comments @@ -1219,7 +1219,6 @@ function Base.read!(io::IO, model::Model) parse_indicators_line(data, items) else @assert header == HEADER_ENDATA - break end end copy_to(model, data) @@ -1556,7 +1555,7 @@ function parse_single_rhs( data.row_upper[row] = value else @assert data.sense[row] == SENSE_N - error("Cannot have RHS for objective: $(join(items, " "))") + error("Cannot have RHS for free row: $(join(items, " "))") end return end diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 341418726c..d494a6cb18 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -10,6 +10,8 @@ import LinearAlgebra import MathOptInterface as MOI import MutableArithmetics as MA import OrderedCollections: OrderedDict +import SparseArrays +import Unicode function print_with_acronym(io::IO, s::AbstractString) return print(io, replace_acronym(s)) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 41a2c64717..7f95e220fd 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -341,11 +341,8 @@ function MOI.add_variable(m::CachingOptimizer) vindex_optimizer = MOI.add_variable(m.optimizer)::MOI.VariableIndex catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else vindex_optimizer = MOI.add_variable(m.optimizer)::MOI.VariableIndex @@ -366,11 +363,8 @@ function MOI.add_variables(m::CachingOptimizer, n) vindices_optimizer = MOI.add_variables(m.optimizer, n)::Vector{MOI.VariableIndex} catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else vindices_optimizer = @@ -413,11 +407,8 @@ function MOI.add_constrained_variable( MOI.ConstraintIndex{MOI.VariableIndex,S}, } catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else vindex_optimizer, cindex_optimizer = MOI.add_constrained_variable( @@ -480,11 +471,8 @@ function MOI.add_constrained_variables( MOI.ConstraintIndex{MOI.VectorOfVariables,S}, } catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else vindices_optimizer, cindex_optimizer = @@ -534,14 +522,11 @@ function MOI.add_constraint( set, )::MOI.ConstraintIndex{F,S} catch err - if err isa MOI.NotAllowedError - # It could be MOI.AddConstraintNotAllowed{F', S'} with F' != F - # or S' != S if, for example, the `F`-in-`S` constraint is bridged - # to other constraints in `m.optimizer` - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + # It could be MOI.AddConstraintNotAllowed{F', S'} with F' != F + # or S' != S if, for example, the `F`-in-`S` constraint is bridged + # to other constraints in `m.optimizer` + reset_optimizer(m) end else cindex_optimizer = MOI.add_constraint( @@ -571,11 +556,8 @@ function MOI.modify( try MOI.modify(m.optimizer, cindex_optimizer, change_optimizer) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.modify(m.optimizer, cindex_optimizer, change_optimizer) @@ -607,11 +589,8 @@ function _replace_constraint_function_or_set( replacement_optimizer, ) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.set( @@ -671,11 +650,8 @@ function MOI.modify( try MOI.modify(m.optimizer, obj, change_optimizer) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.modify(m.optimizer, obj, change_optimizer) @@ -700,11 +676,8 @@ function MOI.delete(m::CachingOptimizer, index::MOI.Index) try MOI.delete(m.optimizer, index_optimizer) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.delete(m.optimizer, index_optimizer) @@ -734,11 +707,8 @@ function MOI.delete(m::CachingOptimizer, indices::Vector{<:MOI.Index}) try MOI.delete(m.optimizer, indices_optimizer) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.delete(m.optimizer, indices_optimizer) @@ -771,11 +741,8 @@ function MOI.set(m::CachingOptimizer, attr::MOI.AbstractModelAttribute, value) try MOI.set(m.optimizer, attr, optimizer_value) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.set(m.optimizer, attr, optimizer_value) @@ -798,11 +765,8 @@ function MOI.set( try MOI.set(m.optimizer, attr, optimizer_index, optimizer_value) catch err - if err isa MOI.NotAllowedError - reset_optimizer(m) - else - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) + reset_optimizer(m) end else MOI.set(m.optimizer, attr, optimizer_index, optimizer_value) @@ -831,13 +795,15 @@ function MOI.supports( (m.state == NO_OPTIMIZER || MOI.supports(m.optimizer, attr)::Bool) end +_rethrow_if_not_NotAllowedError(err) = rethrow(err) + +_rethrow_if_not_NotAllowedError(::MOI.NotAllowedError) = nothing + function _get_model_attribute(model::CachingOptimizer, attr::MOI.ObjectiveValue) try return MOI.get(model.optimizer, attr) catch err - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) return get_fallback(model, attr) end end @@ -849,9 +815,7 @@ function _get_model_attribute( try return MOI.get(model.optimizer, attr) catch err - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) MOI.check_result_index_bounds(model, attr) # We don't know what coefficient type to use, so just use whatever the # objective value type is. This is slightly inefficient, but it @@ -996,10 +960,7 @@ function _get_fallback( model.model_to_optimizer_map[index], ) catch err - # Thrown if .optimizer doesn't support attr - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) return get_fallback(model, attr, index) end end @@ -1016,10 +977,7 @@ function _get_fallback( [model.model_to_optimizer_map[i] for i in indices], ) catch err - # Thrown if .optimizer doesn't support attr - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end + _rethrow_if_not_NotAllowedError(err) return [get_fallback(model, attr, i) for i in indices] end end diff --git a/src/Utilities/print.jl b/src/Utilities/print.jl index 0a8f8b2558..c9aff0ea8d 100644 --- a/src/Utilities/print.jl +++ b/src/Utilities/print.jl @@ -683,13 +683,25 @@ end Base.show(io::IO, ::MIME"text/latex", model::_LatexModel) = show(io, model) -function Base.print(model::MOI.ModelLike; kwargs...) +function _get_ijulia_latex_display() for d in Base.Multimedia.displays if Base.Multimedia.displayable(d, "text/latex") && startswith("$(typeof(d))", "IJulia.") - return display(d, "text/latex", latex_formulation(model; kwargs...)) + return d end end + return +end + +function Base.print( + model::MOI.ModelLike; + _latex_display = _get_ijulia_latex_display(), + kwargs..., +) + if _latex_display !== nothing + formulation = latex_formulation(model; kwargs...) + return display(_latex_display, "text/latex", formulation) + end return print(stdout, model; kwargs...) end diff --git a/src/Utilities/sparse_matrix.jl b/src/Utilities/sparse_matrix.jl index a39a450c22..98b4b4195c 100644 --- a/src/Utilities/sparse_matrix.jl +++ b/src/Utilities/sparse_matrix.jl @@ -4,8 +4,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -import SparseArrays - """ abstract type AbstractIndexing end diff --git a/src/Utilities/struct_of_constraints.jl b/src/Utilities/struct_of_constraints.jl index 515bde35df..f5622c0ab5 100644 --- a/src/Utilities/struct_of_constraints.jl +++ b/src/Utilities/struct_of_constraints.jl @@ -213,9 +213,6 @@ end _typed(s::SymbolFS) = s.typed ? Expr(:curly, esc(s.s), esc(:T)) : esc(s.s) -# Base.lowercase is moved to Unicode.lowercase in Julia v0.7 -import Unicode - function _field(s::SymbolFS) return Symbol(replace(Unicode.lowercase(string(s.s)), "." => "_")) end diff --git a/src/Utilities/variables_container.jl b/src/Utilities/variables_container.jl index 18c65b9226..23f60c71f9 100644 --- a/src/Utilities/variables_container.jl +++ b/src/Utilities/variables_container.jl @@ -117,8 +117,7 @@ end # inference. function _throw_if_lower_bound_set_inner(variable, S2, mask, T) S1 = _flag_to_set_type(mask, T) - throw(MOI.LowerBoundAlreadySet{S1,S2}(variable)) - return + return throw(MOI.LowerBoundAlreadySet{S1,S2}(variable)) end function _throw_if_lower_bound_set(variable, S2, mask, T) @@ -135,8 +134,7 @@ end # inference. function _throw_if_upper_bound_set_inner(variable, S2, mask, T) S1 = _flag_to_set_type(mask, T) - throw(MOI.UpperBoundAlreadySet{S1,S2}(variable)) - return + return throw(MOI.UpperBoundAlreadySet{S1,S2}(variable)) end function _throw_if_upper_bound_set(variable, S2, mask, T) diff --git a/src/indextypes.jl b/src/indextypes.jl index ce795a705b..4fa471dbab 100644 --- a/src/indextypes.jl +++ b/src/indextypes.jl @@ -93,6 +93,7 @@ end function Base.showerror(io::IO, err::InvalidIndex) return print( + io, "The index $(err.index) is invalid. Note that an index becomes invalid after it has been deleted.", ) end diff --git a/src/instantiate.jl b/src/instantiate.jl index abf179d272..34c43c0383 100644 --- a/src/instantiate.jl +++ b/src/instantiate.jl @@ -24,6 +24,7 @@ struct OptimizerWithAttributes end _to_param(param::Pair{<:AbstractOptimizerAttribute}) = param + function _to_param(param::Pair{String}) return RawOptimizerAttribute(param.first) => param.second end diff --git a/test/Benchmarks/Benchmarks.jl b/test/Benchmarks/Benchmarks.jl index 9a08f1399e..c2e510f1a2 100644 --- a/test/Benchmarks/Benchmarks.jl +++ b/test/Benchmarks/Benchmarks.jl @@ -43,6 +43,16 @@ function test_baseline() suite = MOI.Benchmarks.suite() do return MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) end + @test_throws( + ErrorException("You create a baseline with `create_baseline` first."), + MOI.Benchmarks.compare_against_baseline( + suite, + "baseline"; + directory = @__DIR__, + samples = 1, + verbose = true, + ), + ) MOI.Benchmarks.create_baseline( suite, "baseline"; diff --git a/test/FileFormats/FileFormats.jl b/test/FileFormats/FileFormats.jl index f1a5fe085a..66d6849029 100644 --- a/test/FileFormats/FileFormats.jl +++ b/test/FileFormats/FileFormats.jl @@ -189,6 +189,19 @@ function test_generic_names() return end +function test_unique_names() + model = MOI.Utilities.Model{Float64}() + x = MOI.add_variables(model, 3) + MOI.set.(model, MOI.VariableName(), x, "x") + c = MOI.add_constraint.(model, 1.0 .* x, MOI.EqualTo(1.0)) + MOI.set.(model, MOI.ConstraintName(), c, "c") + MOI.set(model, MOI.ConstraintName(), c[2], "c_1") + MOI.FileFormats.create_unique_names(model) + @test MOI.get.(model, MOI.VariableName(), x) == ["x", "x_1", "x_2"] + @test MOI.get.(model, MOI.ConstraintName(), c) == ["c", "c_1", "c_2"] + return +end + end TestFileFormats.runtests() diff --git a/test/FileFormats/LP/LP.jl b/test/FileFormats/LP/LP.jl index 530c670a1c..ade9c9965e 100644 --- a/test/FileFormats/LP/LP.jl +++ b/test/FileFormats/LP/LP.jl @@ -1099,6 +1099,20 @@ function test_comprehensive_write() return end +function test_unable_to_parse_bound() + io = IOBuffer(""" + minimize + obj: 1 x + subject to + bounds + x + end + """) + model = LP.Model() + @test_throws(ErrorException("Unable to parse bound: x"), read!(io, model)) + return +end + end # module TestLP.runtests() diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 82ee41a56d..b7eb6af276 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -1484,6 +1484,55 @@ function test_model_not_empty() return end +function test_rhs_free_row() + io = IOBuffer(""" + NAME + ROWS + N OBJ + N c + COLUMNS + x OBJ 1 + x c 1 + RHS + rhs c 1 + RANGES + BOUNDS + FR bounds x + ENDATA + """) + model = MPS.Model() + @test_throws( + ErrorException("Cannot have RHS for free row: rhs c 1"), + read!(io, model), + ) + return +end + +function test_malformed_indicator() + io = IOBuffer(""" + NAME + ROWS + N OBJ + COLUMNS + x OBJ 1 + y OBJ 1 + RHS + RANGES + BOUNDS + FR bounds x + BV bounds y + INDICATORS + IF c1 y + ENDATA + """) + model = MPS.Model() + @test_throws( + ErrorException("Malformed INDICATORS line: IF c1 y"), + read!(io, model), + ) + return +end + end # TestMPS TestMPS.runtests() diff --git a/test/Test/Test.jl b/test/Test/Test.jl index d3238df04a..d96c589fca 100644 --- a/test/Test/Test.jl +++ b/test/Test/Test.jl @@ -125,6 +125,15 @@ end @test_logs MOI.Test.runtests(model, config; exclude = exclude) end +@testset "test_FeasibilitySenseEvaluator" begin + evaluator = MOI.Test.FeasibilitySenseEvaluator(true) + @test MOI.features_available(evaluator) == [:Grad, :Jac, :Hess, :ExprGraph] + @test MOI.hessian_lagrangian_structure(evaluator) == [(1, 1)] + evaluator = MOI.Test.FeasibilitySenseEvaluator(false) + @test MOI.features_available(evaluator) == [:Grad, :Jac, :ExprGraph] + @test_throws AssertionError MOI.hessian_lagrangian_structure(evaluator) +end + @testset "test_HS071_evaluator" begin evaluator = MOI.Test.HS071(true, true) features = [:Grad, :Jac, :JacVec, :ExprGraph, :Hess, :HessVec] diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 13a5313dcc..0a3bfe2aa7 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -1187,7 +1187,7 @@ function test_modify_constraint() return end -function test_modify_constraint() +function test_modify_objective() for mode in (MOI.Utilities.AUTOMATIC, MOI.Utilities.MANUAL) cache = MOI.Utilities.Model{Float64}() optimizer = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) @@ -1301,6 +1301,95 @@ function test_get_AttributeFromModelCache() return end +function test_copy_to_attached() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + x = MOI.add_variable(model) + MOI.Utilities.attach_optimizer(model) + src = MOI.Utilities.Model{Float64}() + x = MOI.add_variable(src) + index_map = MOI.copy_to(model, src) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 1 + return +end + +function test_add_variable_not_allowed() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + add_var_allowed = false, + ) + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + MOI.Utilities.attach_optimizer(model) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + x = MOI.add_variable(model) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 1 + return +end + +function test_add_variables_not_allowed() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + add_var_allowed = false, + ) + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + MOI.Utilities.attach_optimizer(model) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + x = MOI.add_variables(model, 2) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 2 + return +end + +function test_add_constrained_variable_not_allowed() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + add_var_allowed = false, + ) + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + MOI.Utilities.attach_optimizer(model) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + x, _ = MOI.add_constrained_variable(model, MOI.ZeroOne()) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 1 + return +end + +function test_add_constrained_variables_not_allowed() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + add_var_allowed = false, + ) + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + MOI.Utilities.attach_optimizer(model) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + x, _ = MOI.add_constrained_variables(model, MOI.Zeros(2)) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 2 + return +end + +function test_delete_not_allowed() + cache = MOI.Utilities.Model{Float64}() + optimizer = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + optimizer.delete_allowed = false + model = MOI.Utilities.CachingOptimizer(cache, optimizer) + MOI.Utilities.attach_optimizer(model) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + x, _ = MOI.add_constrained_variable(model, MOI.ZeroOne()) + @test model.state == MOI.Utilities.ATTACHED_OPTIMIZER + MOI.delete(model, [x]) + @test model.state == MOI.Utilities.EMPTY_OPTIMIZER + @test MOI.get(model.model_cache, MOI.NumberOfVariables()) == 0 + return +end + end # module TestCachingOptimizer.runtests() diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 3a41bd5509..c53a0d9a27 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -2238,6 +2238,40 @@ function test_filter_variables_variable_index() return end +function test_filter_variables_vector_of_variables() + f = MOI.VectorOfVariables(MOI.VariableIndex.(1:2)) + s = MOI.Zeros(2) + g, new_s = MOI.Utilities.filter_variables(x -> x.value != 1, f, s) + @test g == MOI.VectorOfVariables([MOI.VariableIndex(2)]) + @test new_s == MOI.Zeros(1) + return +end + +function test_mult_bool() + f = 1.0 * MOI.VariableIndex(1) + 2.0 + @test *(f, false) ≈ zero(MOI.ScalarAffineFunction{Float64}) + return +end + +function test_is_complex() + @test !MOI.Utilities.is_complex(MOI.ScalarAffineFunction{Float64}) + @test !MOI.Utilities.is_complex(MOI.ScalarQuadraticFunction{Float64}) + @test !MOI.Utilities.is_complex(MOI.VectorAffineFunction{Float64}) + @test !MOI.Utilities.is_complex(MOI.VectorQuadraticFunction{Float64}) + @test MOI.Utilities.is_complex(MOI.ScalarAffineFunction{ComplexF64}) + @test MOI.Utilities.is_complex(MOI.ScalarQuadraticFunction{ComplexF64}) + @test MOI.Utilities.is_complex(MOI.VectorAffineFunction{ComplexF64}) + @test MOI.Utilities.is_complex(MOI.VectorQuadraticFunction{ComplexF64}) + return +end + +function test_ndims() + x = MOI.VariableIndex(1) + @test ndims(1.0 * x) == 0 + @test ndims(MOI.ScalarAffineFunction{Float64}) == 0 + return +end + end # module TestUtilitiesFunctions.runtests() diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 7896800594..96069f4c63 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -689,6 +689,31 @@ function test_modify_set_constants() return end +function test_unsupported_constraint() + model = _new_ScalarSets() + x = MOI.VariableIndex(1) + @test_throws( + MOI.UnsupportedConstraint{MOI.VariableIndex,MOI.ZeroOne}(), + MOI.add_constraint(model.constraints, x, MOI.ZeroOne()), + ) + src = MOI.Utilities.Model{Float64}() + index_map = MOI.Utilities.IndexMap() + @test_throws( + MOI.UnsupportedConstraint{MOI.VariableIndex,MOI.ZeroOne}(), + MOI.Utilities._allocate_constraints( + model.constraints, + src, + index_map, + MOI.VariableIndex, + MOI.ZeroOne, + ), + ) + MOI.Utilities.final_touch(model, index_map) + @test_throws AssertionError MOI.Utilities.final_touch(model, index_map) + MOI.Utilities.final_touch(model, nothing) + return +end + end TestMatrixOfConstraints.runtests() diff --git a/test/Utilities/mockoptimizer.jl b/test/Utilities/mockoptimizer.jl index f39f7a2f65..0c1f0f2848 100644 --- a/test/Utilities/mockoptimizer.jl +++ b/test/Utilities/mockoptimizer.jl @@ -177,6 +177,7 @@ function test_MockConstraintAttribute() mock = MOIU.MockOptimizer(MOIU.Model{Int}()) x = MOI.add_variable(mock) c = MOI.add_constraint(mock, x, MOI.LessThan(0)) + @test MOI.supports(mock, MOI.Utilities.MockConstraintAttribute(), typeof(c)) MOI.set(mock, MOI.Utilities.MockConstraintAttribute(), c, 1) @test MOI.get(mock, MOI.Utilities.MockConstraintAttribute(), c) == 1 return @@ -189,6 +190,57 @@ function test_DualObjectiveValue() return end +function test_add_con_allowed_false() + model = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + add_con_allowed = false, + ) + x = MOI.add_variable(model) + @test_throws( + MOI.AddConstraintNotAllowed, + MOI.add_constraint(model, x, MOI.ZeroOne()), + ) + @test_throws( + MOI.AddConstraintNotAllowed, + MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(1.0)), + ) + return +end + +function test_supports_names_false() + model = MOI.Utilities.MockOptimizer( + MOI.Utilities.Model{Float64}(); + supports_names = false, + ) + @test_throws( + MOI.UnsupportedAttribute{MOI.Name}, + MOI.set(model, MOI.Name(), "model"), + ) + x = MOI.add_variable(model) + @test_throws( + MOI.UnsupportedAttribute{MOI.VariableName}, + MOI.set(model, MOI.VariableName(), x, "x"), + ) + c = MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(1.0)) + @test_throws( + MOI.UnsupportedAttribute{MOI.ConstraintName}, + MOI.set(model, MOI.ConstraintName(), c, "c"), + ) + return +end + +function test_delete_allowed_false() + model = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + model.delete_allowed = false + x = MOI.add_variable(model) + @test_throws(MOI.DeleteNotAllowed{MOI.VariableIndex}, MOI.delete(model, x)) + @test_throws( + MOI.DeleteNotAllowed{MOI.VariableIndex}, + MOI.delete(model, [x]), + ) + return +end + end # module TestMockOptimizer.runtests() diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index 96a66b79a2..ef5d650467 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -562,6 +562,14 @@ function test_indicator_constraints() return end +function test_set_unsupported_objective() + model = MOI.Utilities.Model{Float64}() + f = FunctionNotSupportedBySolvers(MOI.VariableIndex(1)) + attr = MOI.ObjectiveFunction{typeof(f)}() + @test_throws(MOI.UnsupportedAttribute(attr), MOI.set(model, attr, f)) + return +end + end # module TestModel.runtests() diff --git a/test/Utilities/parser.jl b/test/Utilities/parser.jl index 6f5493d2df..4c46501a4b 100644 --- a/test/Utilities/parser.jl +++ b/test/Utilities/parser.jl @@ -435,6 +435,15 @@ function test_parse_scope() return end +function test_parse_unrecognized_expression() + model = MOI.Utilities.Model{Float64}() + @test_throws( + ErrorException("Unrecognized expression []"), + MOI.Utilities.loadfromstring!(model, "[]"), + ) + return +end + end # module TestParser.runtests() diff --git a/test/Utilities/print.jl b/test/Utilities/print.jl index 5f7a085324..3ea458ff83 100644 --- a/test/Utilities/print.jl +++ b/test/Utilities/print.jl @@ -724,6 +724,33 @@ function test_show_2505() return end +function test_print_model_to_stdout() + model = MOI.Utilities.Model{Float64}() + dir = mktempdir() + d = MOI.Utilities._get_ijulia_latex_display() + # d is only not nothing if IJulia is running. + if d === nothing + open(joinpath(dir, "stdout.log"), "w") do io + redirect_stdout(io) do + print(model) + return + end + return + end + @test read(joinpath(dir, "stdout.log"), String) == + "Feasibility\n\nSubject to:\n" + end + io = IOBuffer() + d = Base.Multimedia.TextDisplay(io) + @test Base.Multimedia.displayable(d, "text/latex") + print(model; _latex_display = d) + seekstart(io) + prefix = VERSION < v"1.7" ? "" : "\n" + @test read(io, String) == + "\$\$ \\begin{aligned}\n\\text{feasibility}\\\\\n\\text{Subject to}\\\\\n\\end{aligned} \$\$$(prefix)" + return +end + end TestPrint.runtests() diff --git a/test/Utilities/sparse_matrix.jl b/test/Utilities/sparse_matrix.jl index 3fb9f2e7f2..e30d8aa80c 100644 --- a/test/Utilities/sparse_matrix.jl +++ b/test/Utilities/sparse_matrix.jl @@ -122,6 +122,11 @@ function test_extract_function() empty_f = MOI.Utilities.extract_function(A, 1:0, Float64[]) @test empty_f ≈ MOI.VectorAffineFunction(MOI.VectorAffineTerm{Float64}[], Float64[]) + r10 = MOI.Utilities.extract_function(A, 10, 0.5) + @test r10 ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.5) + f1 = 0.6 + 1.0 * x[1] + 1.0 * x[2] + 1.0 * x[3] + r10 = MOI.Utilities.extract_function(A, 0:1, [0.5, 0.6]) + @test r10 ≈ MOI.Utilities.vectorize([0.5, f1]) return end diff --git a/test/Utilities/test_operate!.jl b/test/Utilities/test_operate!.jl index 3c22ff900e..ae93b82f84 100644 --- a/test/Utilities/test_operate!.jl +++ b/test/Utilities/test_operate!.jl @@ -340,6 +340,14 @@ function test_operate_3b_scalarnonlinearfunction_specialcase() return end +function test_operate_3c() + x = MOI.VariableIndex(1) + f = 1.0 * x + 2.0 + g = MOI.Utilities.operate(*, Float64, f, x) + @test g ≈ 1.0 * x * x + 2.0 * x + return +end + function test_operate_4a() T = Float64 for (f, g) in ( @@ -387,6 +395,7 @@ function test_operate_5a() args = (_test_function(f), _test_function(g)) @test MOI.Utilities.operate!(vcat, T, args...) ≈ _test_function(h) end + @test MOI.Utilities.operate(vcat, T) == T[] return end diff --git a/test/Utilities/variable_container.jl b/test/Utilities/variable_container.jl index 785760e0c3..98181b6563 100644 --- a/test/Utilities/variable_container.jl +++ b/test/Utilities/variable_container.jl @@ -98,6 +98,8 @@ function test_delete_constraint_LessThan() @test MOI.is_valid(a, c) MOI.delete(a, c) @test !MOI.is_valid(a, c) + ci = MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}(1) + @test !MOI.is_valid(a, ci) return end @@ -314,6 +316,37 @@ function test_ListOfConstraintTypesPresent_2() return end +function test_LowerBoundAlreadySet() + for set in ( + MOI.EqualTo(1), + MOI.GreaterThan(1), + MOI.Interval(1, 3), + MOI.Parameter(1), + MOI.Semicontinuous(1, 3), + MOI.Semiinteger(1, 3), + ) + model = MOI.Utilities.VariablesContainer{Int}() + x = MOI.add_variable(model) + c = MOI.add_constraint(model, x, set) + @test_throws( + MOI.LowerBoundAlreadySet, + MOI.add_constraint(model, x, MOI.EqualTo(2)), + ) + end + return +end + +function test_UnsupportedConstraint() + model = MOI.Utilities.VariablesContainer{Int}() + x = [MOI.add_variable(model) for _ in 1:3] + f = MOI.VectorOfVariables(x) + @test_throws( + MOI.UnsupportedConstraint{MOI.VectorOfVariables,MOI.ExponentialCone}, + MOI.add_constraint(model, f, MOI.ExponentialCone()), + ) + return +end + end # module TestVariableContainer.runtests() diff --git a/test/attributes.jl b/test/attributes.jl index 8fd5d8641c..101af29ec6 100644 --- a/test/attributes.jl +++ b/test/attributes.jl @@ -11,6 +11,16 @@ import MathOptInterface as MOI include("dummy.jl") +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$name", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end +end + function test_attributes_is_set_by_optimize() @test MOI.is_set_by_optimize(MOI.TerminationStatus()) @test !MOI.is_set_by_optimize(MOI.ConstraintSet()) @@ -333,16 +343,66 @@ function test_empty_vector_attribute() return end -function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$name", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end - end +function test_broadcastable_submittable() + submit = MOI.LazyConstraint(1) + b = Base.broadcastable(submit) + @test b isa Base.RefValue + @test b[] == submit + return end +function test_submit_not_allowed() + submit = MOI.LazyConstraint(1) + @test MOI.SubmitNotAllowed(submit) == MOI.SubmitNotAllowed(submit, "") + err = MOI.SubmitNotAllowed(submit, "msg") + contents = sprint(showerror, err) + @test occursin("Submitting $submit cannot be performed: msg", contents) + return end +struct ModelWithSupportedSubmittable <: MOI.ModelLike end + +MOI.supports(::ModelWithSupportedSubmittable, ::MOI.LazyConstraint) = true + +function test_submit_argument_error() + model = ModelWithSupportedSubmittable() + submit = MOI.LazyConstraint(1) + @test MOI.supports(model, submit) + @test_throws ArgumentError MOI.submit(model, submit, false) + return +end + +function test_showerror_OptimizeInProgress() + err = MOI.OptimizeInProgress(MOI.VariablePrimal()) + @test sprint(showerror, err) == + "$(typeof(err)): Cannot get result as the `MOI.optimize!` has not finished." + return +end + +function test_showerror_FunctionTypeMismatch() + F, G = MOI.VariableIndex, MOI.VectorOfVariables + contents = sprint(showerror, MOI.FunctionTypeMismatch{F,G}()) + @test occursin("Cannot modify functions of different types", contents) + return +end + +function test_showerror_SetTypeMismatch() + F, G = MOI.ZeroOne, MOI.Integer + contents = sprint(showerror, MOI.SetTypeMismatch{F,G}()) + @test occursin("Cannot modify sets of different types", contents) + return +end + +function test_NLPBlockDual_is_set_by_optimize() + @test MOI.is_set_by_optimize(MOI.NLPBlockDual()) + return +end + +function test_CallbackVariablePrimal_is_set_by_optimize() + @test MOI.is_set_by_optimize(MOI.CallbackVariablePrimal(nothing)) + return +end + +end # module + TestAttributes.runtests() diff --git a/test/errors.jl b/test/errors.jl index baeedd87b1..45686a7d78 100644 --- a/test/errors.jl +++ b/test/errors.jl @@ -11,6 +11,16 @@ import MathOptInterface as MOI include("dummy.jl") +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$name", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end +end + function test_errors_fallback_AddVariableNotAllowed() model = DummyModel() @test_throws MOI.AddVariableNotAllowed MOI.add_variable(model) @@ -370,16 +380,22 @@ function test_ScalarFunctionConstantNotZero_equality() return end -function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$name", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end - end +function test_showerror_InvalidIndex() + x = MOI.VariableIndex(1) + @test sprint(showerror, MOI.InvalidIndex(x)) == + "The index $x is invalid. Note that an index becomes invalid after it has been deleted." + return end +struct ModelWithNoIsValid <: MOI.ModelLike end + +function test_isvalid_fallback() + model = ModelWithNoIsValid() + x = MOI.VariableIndex(1) + @test !MOI.is_valid(model, x) + return end +end # module + TestErrors.runtests() diff --git a/test/instantiate.jl b/test/instantiate.jl index 863f632d73..b39eef9852 100644 --- a/test/instantiate.jl +++ b/test/instantiate.jl @@ -166,6 +166,16 @@ function test_instantiate_with_cache_type() return end +function test_to_param_OptimizerWithAttributes() + @test_throws( + ErrorException( + "Expected an optimizer attribute or a string, got `1` which is a `$(Int)`.", + ), + MOI.OptimizerWithAttributes(MOI.Utilities.Model{Float64}, 1 => 2), + ) + return +end + end TestInstantiate.runtests() diff --git a/test/sets.jl b/test/sets.jl index bf52326974..f4c86417b3 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -377,6 +377,40 @@ function test_interval_promote() return end +mutable struct TestSetNoCopy <: MOI.AbstractVectorSet + data::BigInt end +function test_set_not_isbitstype_copy() + @test !isbitstype(TestSetNoCopy) + @test_throws( + ErrorException( + "Base.copy(::$TestSetNoCopy) is not implemented for this set.", + ), + copy(TestSetNoCopy(BigInt(2))), + ) + return +end + +function test_deprecated_PositiveSemidefiniteConeTriangle() + set = MOI.Scaled(MOI.PositiveSemidefiniteConeTriangle(2)) + @test set.side_dimension == 2 + @test !hasfield(typeof(set), :side_dimension) + return +end + +function test_update_dimension() + @test !MOI.supports_dimension_update(MOI.ExponentialCone) + for S in (MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives) + @test MOI.supports_dimension_update(S) + set = S(1) + new_set = MOI.update_dimension(set, 2) + @test MOI.dimension(set) == 1 + @test MOI.dimension(new_set) == 2 + end + return +end + +end # module + TestSets.runtests()