From f021eab92f5598005b4f3e3c6eaf789ead5e753f Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 11 May 2021 13:53:44 +1200 Subject: [PATCH] Add docs and breaking tests --- docs/src/manual/implementing.md | 5 ++ docs/src/reference/models.md | 1 + src/Test/UnitTests/attributes.jl | 85 +++++++++++++++++++++-------- test/Test/unit.jl | 22 ++++++++ test/Utilities/cachingoptimizer.jl | 12 +++- test/Utilities/mockoptimizer.jl | 28 ++++------ test/Utilities/universalfallback.jl | 12 +++- 7 files changed, 124 insertions(+), 41 deletions(-) diff --git a/docs/src/manual/implementing.md b/docs/src/manual/implementing.md index 336eca9654..08ea407f8b 100644 --- a/docs/src/manual/implementing.md +++ b/docs/src/manual/implementing.md @@ -271,6 +271,11 @@ For each attribute * [`supports`](@ref) returns a `Bool` indicating whether the solver supports the attribute. +!!! info + Use [`attribute_value_type`](@ref) to check the value expected by a given + attribute. You should make sure that your [`get`](@ref) function correctly + infers to this type (or a subtype of it). + Each column in the table indicates whether you need to implement the particular method for each attribute. diff --git a/docs/src/reference/models.md b/docs/src/reference/models.md index 76f9a9154b..7efc192469 100644 --- a/docs/src/reference/models.md +++ b/docs/src/reference/models.md @@ -18,6 +18,7 @@ get get! set supports +attribute_value_type ``` ## Model interface diff --git a/src/Test/UnitTests/attributes.jl b/src/Test/UnitTests/attributes.jl index dab33b694a..381fc66a97 100644 --- a/src/Test/UnitTests/attributes.jl +++ b/src/Test/UnitTests/attributes.jl @@ -146,37 +146,54 @@ for attr in ( :Silent, :SimplexIterations, :SolverName, - :SolveTime, + :SolveTimeSec, :TerminationStatus, :TimeLimitSec, ) f = Symbol("test_attribute_$(attr)") - @eval begin - function $(f)(model::MOI.ModelLike, config::TestConfig) + unittests["test_attribute_$(attr)"] = @eval begin + function $(f)(model::MOI.ModelLike, config::Config) MOI.empty!(model) - MOI.optimize!(model) attribute = MOI.$(attr)() + if MOI.is_set_by_optimize(attribute) + if !config.solve + return + end + MOI.optimize!(model) + end T = MOI.attribute_value_type(attribute) + try + MOI.get(model, attribute) + catch err + if err isa ArgumentError + return # Solver does not support accessing the attribute. + end + end + @static if VERSION < v"1.5" @test MOI.get(model, attribute) isa T else @test @inferred(T, MOI.get(model, attribute)) isa T end end - # unittests["test_attribute_$(attr)"] end end # Variable attributes. for attr in (:VariableName,) f = Symbol("test_attribute_$(attr)") - @eval begin - function $(f)(model::MOI.ModelLike, config::TestConfig) + unittests["test_attribute_$(attr)"] = @eval begin + function $(f)(model::MOI.ModelLike, config::Config) MOI.empty!(model) x = MOI.add_variable(model) MOI.set(model, MOI.VariableName(), x, "x") - MOI.optimize!(model) attribute = MOI.$(attr)() + if MOI.is_set_by_optimize(attribute) + if !config.solve + return + end + MOI.optimize!(model) + end T = MOI.attribute_value_type(attribute) @static if VERSION < v"1.5" @test MOI.get(model, attribute, x) isa T @@ -184,7 +201,6 @@ for attr in (:VariableName,) @test @inferred(T, MOI.get(model, attribute, x)) isa T end end - # unittests["test_attribute_$(attr)"] end end @@ -196,26 +212,51 @@ for attr in ( :ConstraintSet, ) f = Symbol("test_attribute_$(attr)") - @eval begin - function $(f)(model::MOI.ModelLike, config::TestConfig) + unittests["test_attribute_$(attr)"] = @eval begin + function $(f)(model::MOI.ModelLike, config::Config{T}) where {T} MOI.empty!(model) x = MOI.add_variable(model) - ci = MOI.add_constraint( - model, - MOI.SingleVariable(x), - MOI.GreaterThan(0.0), - ) - MOI.optimize!(model) - MOI.set(model, MOI.ConstraintName(), ci, "ci") + ci = + if MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{T}, + MOI.GreaterThan{T}, + ) + MOI.add_constraint( + model, + MOI.ScalarAffineFunction( + [MOI.ScalarAffineTerm(one(T), x)], + zero(T), + ), + MOI.GreaterThan(zero(T)), + ) + elseif MOI.supports_constraint( + model, + MOI.VectorOfVariables, + MOI.Nonnegatives, + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables([x]), + MOI.Nonnegatives(1), + ) + else + return + end attribute = MOI.$(attr)() - T = MOI.attribute_value_type(attribute) + if MOI.is_set_by_optimize(attribute) + if !config.solve + return + end + MOI.optimize!(model) + end + VT = MOI.attribute_value_type(attribute) @static if VERSION < v"1.5" - @test MOI.get(model, attribute, ci) isa T + @test MOI.get(model, attribute, ci) isa VT else - @test @inferred(T, MOI.get(model, attribute, ci)) isa T + @test @inferred(T, MOI.get(model, attribute, ci)) isa VT end end - # unittests["test_attribute_$(attr)"] end end diff --git a/test/Test/unit.jl b/test/Test/unit.jl index 961b3476bc..a71d007820 100644 --- a/test/Test/unit.jl +++ b/test/Test/unit.jl @@ -67,6 +67,28 @@ end "solve_farkas_variable_lessthan", "solve_farkas_variable_lessthan_max", "solve_twice", + "test_attribute_BarrierIterations", + "test_attribute_ConflictStatus", + "test_attribute_DualStatus", + "test_attribute_Name", + "test_attribute_NodeCount", + "test_attribute_NumberOfThreads", + "test_attribute_NumberOfVariables", + "test_attribute_ObjectiveFunctionType", + "test_attribute_ObjectiveSense", + "test_attribute_PrimalStatus", + "test_attribute_RelativeGap", + "test_attribute_ResultCount", + "test_attribute_Silent", + "test_attribute_SimplexIterations", + "test_attribute_SolverName", + "test_attribute_SolveTimeSec", + "test_attribute_TerminationStatus", + "test_attribute_TimeLimitSec", + "test_attribute_VariableName", + "test_attribute_ConstraintFunction", + "test_attribute_ConstraintName", + "test_attribute_ConstraintSet", ], ) MOI.empty!(model) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 9a2f3d0cb2..7b8c82025b 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -556,7 +556,17 @@ for state in (MOIU.NO_OPTIMIZER, MOIU.EMPTY_OPTIMIZER, MOIU.ATTACHED_OPTIMIZER) config = MOIT.Config(solve = false) @testset "Unit" begin - MOIT.unittest(m, config) + MOIT.unittest( + m, + config, + # Exclude these tests because no optimizer is attached. + [ + "test_attribute_Silent", + "test_attribute_SolverName", + "test_attribute_NumberOfThreads", + "test_attribute_TimeLimitSec", + ], + ) end @testset "Continuous Linear" begin exclude = ["partial_start"] # VariablePrimalStart not supported. diff --git a/test/Utilities/mockoptimizer.jl b/test/Utilities/mockoptimizer.jl index 5a167c24e2..02ee1e23da 100644 --- a/test/Utilities/mockoptimizer.jl +++ b/test/Utilities/mockoptimizer.jl @@ -241,29 +241,23 @@ end model = MOI.Utilities.MockOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) - config = MOI.Test.TestConfig() + config = MOI.Test.Config() MOI.set(model, MOI.NumberOfThreads(), 4) MOI.set(model, MOI.Silent(), true) + MOI.set(model, MOI.Name(), "Mock") + MOI.set(model, MOI.TimeLimitSec(), 1.0) MOI.Utilities.set_mock_optimize!( model, mock -> MOI.set(mock, MOI.BarrierIterations(), 1), - mock -> nothing, # ConflictStatus - mock -> nothing, # DualStatus - mock -> MOI.set(mock, MOI.Name(), "Mock"), + mock -> nothing, + mock -> nothing, mock -> MOI.set(mock, MOI.NodeCount(), 1), - mock -> nothing, # NumberOfThreads - mock -> nothing, # NumberOfVariables - mock -> nothing, # ObjectiveFunctionType - mock -> nothing, # ObjectiveSense - mock -> nothing, # PrimalStatus + mock -> nothing, mock -> MOI.set(mock, MOI.RelativeGap(), 0.0), - mock -> nothing, # ResultCount - mock -> nothing, # Silent - mock -> MOI.set(mock, MOI.SimplexIterations(), 1), mock -> nothing, - mock -> MOI.set(mock, MOI.SolveTime(), 1.0), + mock -> MOI.set(mock, MOI.SimplexIterations(), 1), + mock -> MOI.set(mock, MOI.SolveTimeSec(), 1.0), mock -> MOI.set(mock, MOI.TerminationStatus(), MOI.OPTIMAL), - mock -> MOI.set(mock, MOI.TimeLimitSec(), 1.0), ) MOI.Test.test_attribute_BarrierIterations(model, config) MOI.Test.test_attribute_ConflictStatus(model, config) @@ -280,7 +274,7 @@ end MOI.Test.test_attribute_Silent(model, config) MOI.Test.test_attribute_SimplexIterations(model, config) MOI.Test.test_attribute_SolverName(model, config) - MOI.Test.test_attribute_SolveTime(model, config) + MOI.Test.test_attribute_SolveTimeSec(model, config) MOI.Test.test_attribute_TerminationStatus(model, config) MOI.Test.test_attribute_TimeLimitSec(model, config) end @@ -289,7 +283,7 @@ end model = MOI.Utilities.MockOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) - config = MOI.Test.TestConfig() + config = MOI.Test.Config() MOI.Test.test_attribute_VariableName(model, config) end @@ -297,7 +291,7 @@ end model = MOI.Utilities.MockOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) - config = MOI.Test.TestConfig() + config = MOI.Test.Config() MOI.Test.test_attribute_ConstraintFunction(model, config) MOI.Test.test_attribute_ConstraintName(model, config) MOI.Test.test_attribute_ConstraintSet(model, config) diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 2df241592f..1cb7d3babf 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -291,7 +291,17 @@ config = MOIT.Config(solve = false) @test MOI.is_empty(uf) end @testset "Unit" begin - MOIT.unittest(uf, config) + MOIT.unittest( + uf, + config, + # Exclude these tests because no optimizer is attached. + [ + "test_attribute_Silent", + "test_attribute_SolverName", + "test_attribute_NumberOfThreads", + "test_attribute_TimeLimitSec", + ], + ) end @testset "Modification" begin MOIT.modificationtest(uf, config)