diff --git a/docs/src/submodules/Test/overview.md b/docs/src/submodules/Test/overview.md index c6938f9880..bc80373f0f 100644 --- a/docs/src/submodules/Test/overview.md +++ b/docs/src/submodules/Test/overview.md @@ -20,9 +20,7 @@ so that all solvers can benefit. ## How to test a solver The skeleton below can be used for the wrapper test file of a solver named -`FooBar`. Remove unnecessary tests as appropriate, for example tests for -features that the solver does not support (tests are not skipped depending -on the value of `supports`). +`FooBar`. ```julia # ============================ /test/MOI_wrapper.jl ============================ @@ -34,15 +32,15 @@ using Test const MOI = MathOptInterface -const OPTIMIZER_CONSTRUCTOR = MOI.OptimizerWithAttributes( - FooBar.Optimizer, - MOI.Silent() => true +const OPTIMIZER = MOI.instantiate( + MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true), ) -const OPTIMIZER = MOI.instantiate(OPTIMIZER_CONSTRUCTOR) const BRIDGED = MOI.instantiate( - OPTIMIZER_CONSTRUCTOR, with_bridge_type = Float64 + MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true), + with_bridge_type = Float64, ) + const CONFIG = MOI.Test.Config( # Modify tolerances as necessary. atol = 1e-6, @@ -57,196 +55,184 @@ const CONFIG = MOI.Test.Config( basis = false, ) -function test_SolverName() - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "FooBar" -end +""" + runtests() -function test_supports_incremental_interface() - @test MOI.supports_incremental_interface(OPTIMIZER, false) - # Use `@test !...` if names are not supported - @test MOI.supports_incremental_interface(OPTIMIZER, true) +This function runs all functions in the this Module starting with `test_`. +""" +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_unittest() - # Test all the functions included in dictionary `MOI.Test.unittests`, - # except functions "number_threads" and "solve_qcp_edge_cases." - MOI.Test.unittest( +""" + test_runtests() + +This function runs all the tests in MathOptInterface.Test. + +Pass arguments to `exclude` to skip tests for functionality that is not +implemented or that your solver doesn't support. +""" +function test_runtests() + MOI.Test.runtests( BRIDGED, CONFIG, - ["number_threads", "solve_qcp_edge_cases"] + exclude = [ + "test_NumberOfThreads", + "solve_qcp_edge_cases", + ] ) + return end -function test_modification() - MOI.Test.modificationtest(BRIDGED, CONFIG) -end - -function test_contlinear() - MOI.Test.contlineartest(BRIDGED, CONFIG) -end +""" + test_SolverName() -function test_contquadratictest() - MOI.Test.contquadratictest(OPTIMIZER, CONFIG) +You can also write new tests for solver-specific functionality. Write each new +test as a function with a name beginning with `test_`. +""" +function test_SolverName() + @test MOI.get(FooBar.Optimizer(), MOI.SolverName()) == "FooBar" end -function test_contconic() - MOI.Test.contlineartest(BRIDGED, CONFIG) -end +end # module TestFooBar -function test_intconic() - MOI.Test.intconictest(BRIDGED, CONFIG) -end +# This line at tne end of the file runs all the tests! +TestFooBar.runtests() +``` -function test_default_objective_test() - MOI.Test.default_objective_test(OPTIMIZER) -end +Then modify your `runtests.jl` file to include the `MOI_wrapper.jl` file: +```julia +# ============================ /test/runtests.jl ============================ -function test_default_status_test() - MOI.Test.default_status_test(OPTIMIZER) -end +using Test -function test_nametest() - MOI.Test.nametest(OPTIMIZER) +@testset "MOI" begin + include("test/MOI_wrapper.jl") end +``` -function test_validtest() - MOI.Test.validtest(OPTIMIZER) -end +!!! info + The optimizer `BRIDGED` constructed with [`instantiate`](@ref) + automatically bridges constraints that are not supported by `OPTIMIZER` + using the bridges listed in [Bridges](@ref). It is recommended for an + implementation of MOI to only support constraints that are natively + supported by the solver and let bridges transform the constraint to the + appropriate form. For this reason it is expected that tests may not pass if + `OPTIMIZER` is used instead of `BRIDGED`. -function test_emptytest() - MOI.Test.emptytest(OPTIMIZER) -end +## How to add a test -function test_orderedindicestest() - MOI.Test.orderedindicestest(OPTIMIZER) -end +To detect bugs in solvers, we add new tests to `MOI.Test`. -function test_scalar_function_constant_not_zero() - MOI.Test.scalar_function_constant_not_zero(OPTIMIZER) -end +As an example, ECOS errored calling [`optimize!`](@ref) twice in a row. (See +[ECOS.jl PR #72](https://github.com/jump-dev/ECOS.jl/pull/72).) We could add a +test to ECOS.jl, but that would only stop us from re-introducing the bug to +ECOS.jl in the future, but it would not catch other solvers in the ecosystem +with the same bug! Instead, if we add a test to `MOI.Test`, then all solvers +will also check that they handle a double optimize call! -# This function runs all functions in this module starting with `test_`. -function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$(name)", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end - end -end +For this test, we care about correctness, rather than performance. therefore, we +don't expect solvers to efficiently decide that they have already solved the +problem, only that calling [`optimize!`](@ref) twice doesn't throw an error or +give the wrong answer. -end # module TestFooBar +**Step 1** -TestFooBar.runtests() +Install the `MathOptInterface` julia package in `dev` mode +([ref](https://julialang.github.io/Pkg.jl/v1/managing-packages/#developing-1)): +```julia +julia> ] +(@v1.6) pkg> dev MathOptInterface ``` -Test functions like `MOI.Test.unittest` and `MOI.Test.modificationtest` are -wrappers around corresponding dictionaries `MOI.Test.unittests` and -`MOI.Test.modificationtests`. Exclude tests by passing a vector of strings -corresponding to the test keys you want to exclude as the third positional -argument to the test function. +**Step 2** -!!! tip - Print a list of all keys using `println.(keys(MOI.Test.unittests))` - -The optimizer `BRIDGED` constructed with [`instantiate`](@ref) -automatically bridges constraints that are not supported by `OPTIMIZER` -using the bridges listed in [Bridges](@ref). It is recommended for an -implementation of MOI to only support constraints that are natively supported -by the solver and let bridges transform the constraint to the appropriate form. -For this reason it is expected that tests may not pass if `OPTIMIZER` is used -instead of `BRIDGED`. - -To test that a specific problem can be solved without bridges, a specific test -can be added with `OPTIMIZER` instead of `BRIDGED`. For example: +From here on, proceed with making the following changes in the +`~/.julia/dev/MathOptInterface` folder (or equivalent `dev` path on your +machine). + +**Step 3** + +Since the double-optimize error involves solving an optimization problem, +add a new test to [src/Test/UnitTests/solve.jl](https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/UnitTests/solve.jl). + +The test should be something like ```julia -function test_interval_constraints() - MOI.Test.linear10test(OPTIMIZER, CONFIG) +""" + test_solve_twice(model::MOI.ModelLike, config::Config) + +Test that calling `MOI.optimize!` twice does not error. + +This problem was first detected in ECOS.jl PR#72: +https://github.com/jump-dev/ECOS.jl/pull/72 +""" +function test_solve_twice( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !config.solve + # Use `config` to modify the behavior of the tests. Since this test is + # concerned with `optimize!`, we should skip the test if + # `config.solve == false`. + return + end + # This is important! Make sure to empty the existing model! + MOI.empty!(model) + # Create a simple model. Try to make this as simple as possible so that the + # majority of solvers can run the test. + x = MOI.add_variable(model) + MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(one(T))) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.SingleVariable}(), + MOI.SingleVariable(x), + ) + # The main component of the test: does calling `optimize!` twice error? + MOI.optimize!(model) + MOI.optimize!(model) + # Check we have a solution. + MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + MOI.get(model, MOI.VariablePrimal(), x) == one(T) + return end ``` -checks that `OPTIMIZER` implements support for -[`ScalarAffineFunction`](@ref)-in-[`Interval`](@ref). - -## How to add a test -To give an example, ECOS errored calling [`optimize!`](@ref) twice in a row. -(See [ECOS.jl PR #72](https://github.com/jump-dev/ECOS.jl/pull/72).) +!!! info + Make sure the function is agnoistic to the number type `T`! Don't assume it + is a `Float64` capable solver! -We could add a test to ECOS.jl, but that would only stop us from re-introducing -the bug to ECOS.jl in the future. +We also need to write a test for the test. Place this function immediately below +the test you just wrote in the same file: +```julia +function setup_test( + ::typeof(test_solve_twice), + model::MOI.Utilities.MockOptimizer, + config::Config, +) + MOI.Utilities.set_mock_optimize!( + model, + (mock::MOI.Utilities.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + ), + ) + return +end +``` -Instead, if we add a test to `MOI.Test`, then all solvers will also check that -they handle a double optimize call! +**Step 6** -For this test, we care about correctness, rather than performance. therefore, we -don't expect solvers to efficiently decide that they have already solved the -problem, only that calling [`optimize!`](@ref) twice doesn't throw an error or -give the wrong answer. +Commit the changes to git from `~/.julia/dev/MathOptInterface` and +submit the PR for review. -To resolve this issue, follow these steps (tested on Julia v1.5): - -1. Install the `MathOptInterface` julia package in `dev` mode - ([ref](https://julialang.github.io/Pkg.jl/v1/managing-packages/#developing-1)): - ```julia - julia> ] - (@v1.5) pkg> dev ECOS - (@v1.5) pkg> dev MathOptInterface - ``` -2. From here on, proceed with making the following changes in the - `~/.julia/dev/MathOptInterface` folder (or equivalent `dev` path on your - machine). -3. Since the double-optimize error involves solving an optimization problem, - add a new test to [src/Test/UnitTests/solve.jl](https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/UnitTests/solve.jl). - The test should be something like - ```julia - function solve_twice(model::MOI.ModelLike, config::Config) - MOI.empty!(model) - x = MOI.add_variable(model) - c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x)) - if config.solve - MOI.optimize!(model) - MOI.optimize!(model) - MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL - MOI.get(model, MOI.VariablePrimal(), x) == 1.0 - end - end - unittests["solve_twice"] = solve_twice - ``` -2. Add a test for the test you just wrote. (We test the tests!) - a. Add the name of the test (`"solve_twice"`) to the end of the array in - `MOI.Test.unittest(...)` ([link](https://github.com/jump-dev/MathOptInterface.jl/blob/7543afe4b5151cf36bbd18181c1bb5c83266ae2f/test/Test/unit.jl#L51-L52)). - b. Add a test for the test towards the end of the "Unit Tests" test set - ([link](https://github.com/jump-dev/MathOptInterface.jl/blob/7543afe4b5151cf36bbd18181c1bb5c83266ae2f/test/Test/unit.jl#L394)). - The test should look something like - ```julia - @testset "solve_twice" begin - MOI.Utilities.set_mock_optimize!(mock, - (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ) - ) - MOI.Test.solve_twice(mock, config) - end - ``` - In the above `mock` is a `MOI.Utilities.MockOptimizer` that is defined - tesearlier in the file. In this test, `MOI.Utilities.set_mock_optimize!` loads - `mock` with two results. Each says that the - [`TerminationStatus`](@ref) is `MOI.OPTIMAL`, that the - [`PrimalStatus`](@ref) is `MOI.FEASIBLE_POINT`, and that there is one - variable with a `MOI.VariableValue` or `1.0` -3. Run the tests: - ```julia - (@v1.5) pkg> test ECOS - ``` -4. Finally, commit the changes to git from `~/.julia/dev/MathOptInterface` and - submit the PR for review. +!!! tip + If you need help writing a test, [open an issue on GitHub](https://github.com/jump-dev/MathOptInterface.jl/issues/new), + or ask the [Developer Chatroom](https://gitter.im/JuliaOpt/JuMP.jl) diff --git a/src/Test/Test.jl b/src/Test/Test.jl index b88ad44263..c3d0f58362 100644 --- a/src/Test/Test.jl +++ b/src/Test/Test.jl @@ -6,19 +6,328 @@ const MOIU = MOI.Utilities using Test -include("config.jl") +struct Config{T<:Real} + atol::T + rtol::T + solve::Bool + query_number_of_constraints::Bool + query::Bool + modify_lhs::Bool + duals::Bool + dual_objective_value::Bool + infeas_certificates::Bool + optimal_status::MOI.TerminationStatusCode + basis::Bool -include("modellike.jl") + """ + Config{T}(; + atol::Real = Base.rtoldefault(T), + rtol::Real = Base.rtoldefault(T), + solve::Bool = true, + query_number_of_constraints::Bool = true, + query::Bool = true, + modify_lhs::Bool = true, + duals::Bool = true, + dual_objective_value::Bool = duals, + infeas_certificates::Bool = true, + optimal_status = MOI.OPTIMAL, + basis::Bool = false, + ) + + Return an object that is used to configure various tests. + + ## Keywords + + * `atol::Real = Base.rtoldefault(T)`: Control the absolute tolerance used + when comparing solutions. + * `rtol::Real = Base.rtoldefault(T)`: Control the relative tolerance used + when comparing solutions. + * `solve::Bool = true`: Set to `false` to skip tests requiring a call to + [`MOI.optimize!`](@ref) + * `query_number_of_constraints::Bool = true`: Set to `false` to skip tests + requiring a call to [`MOI.NumberOfConstraints`](@ref). + * `query::Bool = true`: Set to `false` to skip tests requiring a call to + [`MOI.get`](@ref) for [`MOI.ConstraintFunction`](@ref) and + [`MOI.ConstraintSet`](@ref) + * `modify_lhs::Bool = true`: + * `duals::Bool = true`: Set to `false` to skip tests querying + [`MOI.ConstraintDual`](@ref). + * `dual_objective_value::Bool = duals`: Set to `false` to skip tests + querying [`MOI.DualObjectiveValue`](@ref). + * `infeas_certificates::Bool = true`: Set to `false` to skip tests querying + primal and dual infeasibility certificates. + * `optimal_status = MOI.OPTIMAL`: Set to `MOI.LOCALLY_SOLVED` if the solver + cannot prove global optimality. + * `basis::Bool = false`: Set to `true` if the solver supports + [`MOI.ConstraintBasisStatus`](@ref) and [`MOI.VariableBasisStatus`](@ref). + """ + function Config{T}(; + atol::Real = Base.rtoldefault(T), + rtol::Real = Base.rtoldefault(T), + solve::Bool = true, + query_number_of_constraints::Bool = true, + query::Bool = true, + modify_lhs::Bool = true, + duals::Bool = true, + dual_objective_value::Bool = duals, + infeas_certificates::Bool = true, + optimal_status = MOI.OPTIMAL, + basis::Bool = false, + ) where {T<:Real} + return new( + atol, + rtol, + solve, + query_number_of_constraints, + query, + modify_lhs, + duals, + dual_objective_value, + infeas_certificates, + optimal_status, + basis, + ) + end + Config(; kwargs...) = Config{Float64}(; kwargs...) +end + +@deprecate TestConfig Config + +""" + setup_test(::typeof(f), model::MOI.ModelLike, config::Config) + +Overload this method to modify `model` before running the test function `f` on +`model` with `config`. + +This function should either return `nothing`, or return a function which, when +called with zero arguments, undoes the setup to return the model to its +previous state. + +This is most useful when writing new tests of the tests for MOI, but can also be +used to set test-specific tolerances, etc. + +See also: [`runtests`](@ref) + +## Example + +```julia +function MOI.Test.setup_test( + ::typeof(MOI.Test.test_linear_VariablePrimalStart_partial), + mock::MOIU.MockOptimizer, + ::MOI.Test.Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0]), + ) + mock.eval_variable_constraint_dual = false + + function reset_function() + mock.eval_variable_constraint_dual = true + return + end + return reset_function +end +``` +""" +setup_test(::Any, ::MOI.ModelLike, ::Config) = nothing + +""" + runtests( + model::MOI.ModelLike, + config::Config; + include::Vector{String} = String[], + exclude::Vector{String} = String[], + ) + +Run all tests in `MathOptInterface.Test` on `model`. + +## Configuration arguments + + * `config` is a [`Test.Config`](@ref) object that can be used to modify the + behavior of tests. + * If `include` is not empty, only run tests that contain an element from + `include` in their name. + * If `exclude` is not empty, skip tests that contain an element from `exclude` + in their name. + * `exclude` takes priority over `include`. +See also: [`setup_test`](@ref). + +## Example + +```julia +config = MathOptInterface.Test.Config() +MathOptInterface.Test.runtests( + model, + config; + include = ["test_linear_"], + exclude = ["VariablePrimalStart"], +) +``` +""" +function runtests( + model::MOI.ModelLike, + config::Config; + include::Vector{String} = String[], + exclude::Vector{String} = String[], +) + for name_sym in names(@__MODULE__; all = true) + name = string(name_sym) + if !startswith(name, "test_") + continue # All test functions start with test_ + elseif !isempty(include) && !any(s -> occursin(s, name), include) + continue + elseif !isempty(exclude) && any(s -> occursin(s, name), exclude) + continue + end + @testset "$(name)" begin + test_function = getfield(@__MODULE__, name_sym) + tear_down = setup_test(test_function, model, config) + test_function(model, config) + if tear_down !== nothing + tear_down() + end + end + end +end + +""" + _test_model_solution( + model::MOI.ModelLike, + config::Config; + objective_value = nothing, + variable_primal = nothing, + constraint_primal = nothing, + constraint_dual = nothing, + ) + +Solve, and then test, various aspects of a model. + +First, check that `TerminationStatus == MOI.OPTIMAL`. + +If `objective_value` is not nothing, check that the attribute `ObjectiveValue()` +is approximately `objective_value`. + +If `variable_primal` is not nothing, check that the attribute `PrimalStatus` is +`MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `variable_primal`, check +that the primal value of the variable `index` is approximately `value`. + +If `constraint_primal` is not nothing, check that the attribute `PrimalStatus` is +`MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `constraint_primal`, check +that the primal value of the constraint `index` is approximately `value`. + +Finally, if `config.duals = true`, and if `constraint_dual` is not nothing, +check that the attribute `DualStatus` is `MOI.FEASIBLE_POINT`. Then for each +`(index, value)` in `constraint_dual`, check that the dual of the constraint +`index` is approximately `value`. + +### Example + +```julia +MOIU.loadfromstring!(model, \"\"\" + variables: x + minobjective: 2.0x + 1.0 + c: x >= 1.0 +\"\"\") +x = MOI.get(model, MOI.VariableIndex, "x") +c = MOI.get(model, MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}}, "c") +_test_model_solution( + model, + config; + objective_value = 3.0, + variable_primal = [(x, 1.0)], + constraint_primal = [(c, 1.0)], + constraint_dual = [(c, 2.0)], +) +``` +""" +function _test_model_solution( + model, + config; + objective_value = nothing, + variable_primal = nothing, + constraint_primal = nothing, + constraint_dual = nothing, +) + if !config.solve + return + end + atol, rtol = config.atol, config.rtol + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + if objective_value !== nothing + @test MOI.get(model, MOI.ObjectiveValue()) ≈ objective_value atol = atol rtol = + rtol + end + if variable_primal !== nothing + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + for (index, solution_value) in variable_primal + @test MOI.get(model, MOI.VariablePrimal(), index) ≈ solution_value atol = + atol rtol = rtol + end + end + if constraint_primal !== nothing + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + for (index, solution_value) in constraint_primal + @test MOI.get(model, MOI.ConstraintPrimal(), index) ≈ solution_value atol = + atol rtol = rtol + end + end + if config.duals + if constraint_dual !== nothing + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + for (index, solution_value) in constraint_dual + @test MOI.get(model, MOI.ConstraintDual(), index) ≈ + solution_value atol = atol rtol = rtol + end + end + end + return +end + +macro moitestset(setname, subsets = false) + testname = Symbol(string(setname) * "test") + testdict = Symbol(string(testname) * "s") + if subsets + runtest = :(f(model, config, exclude)) + else + runtest = :(f(model, config)) + end + return esc( + :( + function $testname( + model::$MOI.ModelLike, + config::$MOI.Test.Config, + exclude::Vector{String} = String[], + ) + for (name, f) in $testdict + if name in exclude + continue + end + @testset "$name" begin + $runtest + end + end + end + ), + ) +end + +include("modellike.jl") include("contlinear.jl") include("contconic.jl") include("contquadratic.jl") - include("intlinear.jl") include("intconic.jl") - include("nlp.jl") -include("UnitTests/unit_tests.jl") +include("UnitTests/attributes.jl") +include("UnitTests/basic_constraint_tests.jl") +include("UnitTests/constraints.jl") +include("UnitTests/modifications.jl") +include("UnitTests/objectives.jl") +include("UnitTests/solve.jl") +include("UnitTests/variables.jl") end # module diff --git a/src/Test/UnitTests/attributes.jl b/src/Test/UnitTests/attributes.jl index 0de503b44d..7ae7c597a7 100644 --- a/src/Test/UnitTests/attributes.jl +++ b/src/Test/UnitTests/attributes.jl @@ -1,83 +1,118 @@ """ - solver_name(model::MOI.ModelLike, config::Config) + test_attribute_SolverName(model::MOI.ModelLike, config::Config) Test that the [`MOI.SolverName`](@ref) attribute is implemented for `model`. """ -function solver_name(model::MOI.ModelLike, config::Config) - if config.solve - @test MOI.get(model, MOI.SolverName()) isa AbstractString +function test_attribute_SolverName(model::MOI.ModelLike, config::Config) + if !config.solve + return end + @test MOI.get(model, MOI.SolverName()) isa AbstractString + return end -unittests["solver_name"] = solver_name """ - silent(model::MOI.ModelLike, config::Config) + test_attribute_Silent(model::MOI.ModelLike, config::Config) Test that the [`MOI.Silent`](@ref) attribute is implemented for `model`. """ -function silent(model::MOI.ModelLike, config::Config) - if config.solve - @test MOI.supports(model, MOI.Silent()) - # Get the current value to restore it at the end of the test - value = MOI.get(model, MOI.Silent()) - MOI.set(model, MOI.Silent(), !value) - @test !value == MOI.get(model, MOI.Silent()) - # Check that `set` does not just take `!` of the current value - MOI.set(model, MOI.Silent(), !value) - @test !value == MOI.get(model, MOI.Silent()) - MOI.set(model, MOI.Silent(), value) - @test value == MOI.get(model, MOI.Silent()) +function test_attribute_Silent(model::MOI.ModelLike, config::Config) + if !config.solve + return end + @test MOI.supports(model, MOI.Silent()) + # Get the current value to restore it at the end of the test + value = MOI.get(model, MOI.Silent()) + MOI.set(model, MOI.Silent(), !value) + @test !value == MOI.get(model, MOI.Silent()) + # Check that `set` does not just take `!` of the current value + MOI.set(model, MOI.Silent(), !value) + @test !value == MOI.get(model, MOI.Silent()) + MOI.set(model, MOI.Silent(), value) + @test value == MOI.get(model, MOI.Silent()) + return +end + +function setup_test( + ::typeof(test_attribute_Silent), + model::MOIU.MockOptimizer, + ::Config, +) + MOI.set(model, MOI.Silent(), true) + return end -unittests["silent"] = silent """ - time_limit_sec(model::MOI.ModelLike, config::Config) + test_attribute_TimeLimitSec(model::MOI.ModelLike, config::Config) Test that the [`MOI.TimeLimitSec`](@ref) attribute is implemented for `model`. """ -function time_limit_sec(model::MOI.ModelLike, config::Config) - if config.solve - @test MOI.supports(model, MOI.TimeLimitSec()) - # Get the current value to restore it at the end of the test - value = MOI.get(model, MOI.TimeLimitSec()) - MOI.set(model, MOI.TimeLimitSec(), 0.0) - @test MOI.get(model, MOI.TimeLimitSec()) == 0.0 - MOI.set(model, MOI.TimeLimitSec(), 1.0) - @test MOI.get(model, MOI.TimeLimitSec()) == 1.0 - MOI.set(model, MOI.TimeLimitSec(), value) - @test value == MOI.get(model, MOI.TimeLimitSec()) # Equality should hold +function test_attribute_TimeLimitSec(model::MOI.ModelLike, config::Config) + if !config.solve + return end + @test MOI.supports(model, MOI.TimeLimitSec()) + # Get the current value to restore it at the end of the test + value = MOI.get(model, MOI.TimeLimitSec()) + MOI.set(model, MOI.TimeLimitSec(), 0.0) + @test MOI.get(model, MOI.TimeLimitSec()) == 0.0 + MOI.set(model, MOI.TimeLimitSec(), 1.0) + @test MOI.get(model, MOI.TimeLimitSec()) == 1.0 + MOI.set(model, MOI.TimeLimitSec(), value) + @test value == MOI.get(model, MOI.TimeLimitSec()) # Equality should hold + return +end + +function setup_test( + ::typeof(test_attribute_TimeLimitSec), + model::MOIU.MockOptimizer, + ::Config, +) + MOI.set(model, MOI.TimeLimitSec(), nothing) + return end -unittests["time_limit_sec"] = time_limit_sec """ - number_threads(model::MOI.ModelLike, config::Config) + test_attribute_NumberThreads(model::MOI.ModelLike, config::Config) -Test that the [`MOI.NumberOfThreads`](@ref) attribute is implemented for `model`. +Test that the [`MOI.NumberOfThreads`](@ref) attribute is implemented for +`model`. """ -function number_threads(model::MOI.ModelLike, config::Config) - if config.solve - @test MOI.supports(model, MOI.NumberOfThreads()) - # Get the current value to restore it at the end of the test - value = MOI.get(model, MOI.NumberOfThreads()) - MOI.set(model, MOI.NumberOfThreads(), 1) - @test MOI.get(model, MOI.NumberOfThreads()) == 1 - MOI.set(model, MOI.NumberOfThreads(), 3) - @test MOI.get(model, MOI.NumberOfThreads()) == 3 - MOI.set(model, MOI.NumberOfThreads(), value) - @test value == MOI.get(model, MOI.NumberOfThreads()) +function test_attribute_NumberThreads(model::MOI.ModelLike, config::Config) + if !config.solve + return end + @test MOI.supports(model, MOI.NumberOfThreads()) + # Get the current value to restore it at the end of the test + value = MOI.get(model, MOI.NumberOfThreads()) + MOI.set(model, MOI.NumberOfThreads(), 1) + @test MOI.get(model, MOI.NumberOfThreads()) == 1 + MOI.set(model, MOI.NumberOfThreads(), 3) + @test MOI.get(model, MOI.NumberOfThreads()) == 3 + MOI.set(model, MOI.NumberOfThreads(), value) + @test value == MOI.get(model, MOI.NumberOfThreads()) + return +end + +function setup_test( + ::typeof(test_attribute_NumberThreads), + model::MOIU.MockOptimizer, + ::Config, +) + MOI.set(model, MOI.NumberOfThreads(), nothing) + return end -unittests["number_threads"] = number_threads """ - raw_status_string(model::MOI.ModelLike, config::Config) + test_attribute_RawStatusString(model::MOI.ModelLike, config::Config) Test that the [`MOI.RawStatusString`](@ref) attribute is implemented for `model`. """ -function raw_status_string(model::MOI.ModelLike, config::Config) +function test_attribute_RawStatusString(model::MOI.ModelLike, config::Config) + if !config.solve + return + end MOI.empty!(model) @test MOI.is_empty(model) x = MOI.add_variable(model) @@ -88,24 +123,44 @@ function raw_status_string(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - test_model_solution( + _test_model_solution( model, config, objective_value = 0.0, variable_primal = [(x, 0.0)], ) - if config.solve - @test MOI.get(model, MOI.RawStatusString()) isa AbstractString - end + @test MOI.get(model, MOI.RawStatusString()) isa AbstractString + return +end + +function setup_test( + ::typeof(test_attribute_RawStatusString), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set( + mock, + MOI.RawStatusString(), + "Mock solution set by `mock_optimize!`.", + ) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])) + end, + ) + return end -unittests["raw_status_string"] = raw_status_string """ - solve_time(model::MOI.ModelLike, config::Config) + test_attribute_SolveTimeSec(model::MOI.ModelLike, config::Config) Test that the [`MOI.SolveTimeSec`](@ref) attribute is implemented for `model`. """ -function solve_time(model::MOI.ModelLike, config::Config) +function test_attribute_SolveTimeSec(model::MOI.ModelLike, config::Config) + if !config.solve + return + end MOI.empty!(model) @test MOI.is_empty(model) x = MOI.add_variable(model) @@ -116,15 +171,28 @@ function solve_time(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - test_model_solution( + _test_model_solution( model, config, objective_value = 0.0, variable_primal = [(x, 0.0)], ) - if config.solve - time = MOI.get(model, MOI.SolveTimeSec()) - @test time ≥ 0.0 - end + time = MOI.get(model, MOI.SolveTimeSec()) + @test time >= 0.0 + return +end + +function setup_test( + ::typeof(test_attribute_SolveTimeSec), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.SolveTimeSec(), 0.0) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])) + end, + ) + return end -unittests["solve_time"] = solve_time diff --git a/src/Test/UnitTests/constraints.jl b/src/Test/UnitTests/constraints.jl index e3c5abbafe..8bedea5c9b 100644 --- a/src/Test/UnitTests/constraints.jl +++ b/src/Test/UnitTests/constraints.jl @@ -1,9 +1,9 @@ """ - getconstraint(model::MOI.ModelLike, config::Config) + test_get_ConstraintIndex(model::MOI.ModelLike, config::Config) Test getting constraints by name. """ -function getconstraint(model::MOI.ModelLike, config::Config) +function test_get_ConstraintIndex(model::MOI.ModelLike, ::Config) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -29,17 +29,30 @@ function getconstraint(model::MOI.ModelLike, config::Config) c2 = MOI.get(model, MOI.ConstraintIndex{F,MOI.LessThan{Float64}}, "c2") @test MOI.get(model, MOI.ConstraintIndex, "c2") == c2 @test MOI.is_valid(model, c2) + return end -unittests["getconstraint"] = getconstraint """ - solve_affine_lessthan(model::MOI.ModelLike, config::Config) + test_constraint_ScalarAffineFunction_LessThan( + model::MOI.ModelLike, + config::Config, + ) Add an ScalarAffineFunction-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_affine_lessthan(model::MOI.ModelLike, config::Config) +function test_constraint_ScalarAffineFunction_LessThan( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, + ) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -58,7 +71,7 @@ function solve_affine_lessthan(model::MOI.ModelLike, config::Config) }, "c", ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -66,17 +79,49 @@ function solve_affine_lessthan(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, -0.5)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ScalarAffineFunction_LessThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-0.5], + ), + ) + return end -unittests["solve_affine_lessthan"] = solve_affine_lessthan """ - solve_affine_greaterthan(model::MOI.ModelLike, config::Config) + test_constraint_ScalarAffineFunction_GreaterThan( + model::MOI.ModelLike, + config::Config, + ) Add an ScalarAffineFunction-in-GreaterThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_affine_greaterthan(model::MOI.ModelLike, config::Config) +function test_constraint_ScalarAffineFunction_GreaterThan( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -95,7 +140,7 @@ function solve_affine_greaterthan(model::MOI.ModelLike, config::Config) }, "c", ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -103,17 +148,49 @@ function solve_affine_greaterthan(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, 0.5)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ScalarAffineFunction_GreaterThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [0.5], + ), + ) + return end -unittests["solve_affine_greaterthan"] = solve_affine_greaterthan """ - solve_affine_equalto(model::MOI.ModelLike, config::Config) + test_constraint_ScalarAffineFunction_EqualTo( + model::MOI.ModelLike, + config::Config, + ) Add an ScalarAffineFunction-in-EqualTo constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_affine_equalto(model::MOI.ModelLike, config::Config) +function test_constraint_ScalarAffineFunction_EqualTo( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + ) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -132,7 +209,7 @@ function solve_affine_equalto(model::MOI.ModelLike, config::Config) }, "c", ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -140,17 +217,49 @@ function solve_affine_equalto(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, 0.5)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ScalarAffineFunction_EqualTo), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [0.5], + ), + ) + return end -unittests["solve_affine_equalto"] = solve_affine_equalto """ - solve_affine_interval(model::MOI.ModelLike, config::Config) + test_constraint_ScalarAffineFunction_Interval( + model::MOI.ModelLike, + config::Config, + ) Add an ScalarAffineFunction-in-Interval constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_affine_interval(model::MOI.ModelLike, config::Config) +function test_constraint_ScalarAffineFunction_Interval( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{Float64}, + MOI.Interval{Float64}, + ) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -169,7 +278,7 @@ function solve_affine_interval(model::MOI.ModelLike, config::Config) }, "c", ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 6.0, @@ -177,19 +286,41 @@ function solve_affine_interval(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 4.0)], constraint_dual = [(c, -1.5)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ScalarAffineFunction_Interval), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [2.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [-1.5], + ), + ) + return end -unittests["solve_affine_interval"] = solve_affine_interval -# Taken from https://github.com/JuliaOpt/MathOptInterfaceMosek.jl/issues/41 """ - solve_duplicate_terms_scalar_affine(model::MOI.ModelLike, - config::Config) + test_constraint_ScalarAffineFunction_duplicate( + model::MOI.ModelLike, + config::Config, + ) Add a `ScalarAffineFunction`-in-`LessThan` constraint with duplicate terms in the function. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. + +Taken from https://github.com/JuliaOpt/MathOptInterfaceMosek.jl/issues/41 """ -function solve_duplicate_terms_scalar_affine( +function test_constraint_ScalarAffineFunction_duplicate( model::MOI.ModelLike, config::Config, ) @@ -205,7 +336,7 @@ function solve_duplicate_terms_scalar_affine( MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, x]), 0.0) c = MOI.add_constraint(model, f, MOI.LessThan(1.0)) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -213,19 +344,39 @@ function solve_duplicate_terms_scalar_affine( constraint_primal = [(c, 1.0)], constraint_dual = [(c, -0.5)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ScalarAffineFunction_duplicate), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-0.5], + ), + ) + return end -unittests["solve_duplicate_terms_scalar_affine"] = - solve_duplicate_terms_scalar_affine """ - solve_duplicate_terms_vector_affine(model::MOI.ModelLike, - config::Config) + test_constraint_VectorAffineFunction_duplicate( + model::MOI.ModelLike, + config::Config, + ) Add a `VectorAffineFunction`-in-`Nonpositives` constraint with duplicate terms in the function. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_duplicate_terms_vector_affine( +function test_constraint_VectorAffineFunction_duplicate( model::MOI.ModelLike, config::Config, ) @@ -244,7 +395,7 @@ function solve_duplicate_terms_vector_affine( [-1.0], ) c = MOI.add_constraint(model, f, MOI.Nonpositives(1)) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -252,19 +403,36 @@ function solve_duplicate_terms_vector_affine( constraint_primal = [(c, [0.0])], constraint_dual = [(c, [-0.5])], ) + return +end + +function setup_test( + ::typeof(test_constraint_VectorAffineFunction_duplicate), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => + [[-0.5]], + ), + ) + return end -unittests["solve_duplicate_terms_vector_affine"] = - solve_duplicate_terms_vector_affine """ - solve_qcp_edge_cases(model::MOI.ModelLike, config::Config) + test_qcp_duplicate_diagonal(model::MOI.ModelLike, config::Config) -Test various edge cases relating to quadratically constrainted programs (i.e., -with a ScalarQuadraticFunction-in-Set constraint. +Test a QCP problem with a duplicate diagonal term. -If `config.solve=true` confirm that it solves correctly. +The problem is `max x + 2y | y + x^2 + x^2 <= 1, x >= 0.5, y >= 0.5`. """ -function solve_qcp_edge_cases(model::MOI.ModelLike, config::Config) +function test_qcp_duplicate_diagonal(model::MOI.ModelLike, config::Config) if !MOI.supports_constraint( model, MOI.ScalarQuadraticFunction{Float64}, @@ -272,97 +440,141 @@ function solve_qcp_edge_cases(model::MOI.ModelLike, config::Config) ) return end - @testset "Duplicate on-diagonal" begin - # max x + 2y | y + x^2 + x^2 <= 1, x >= 0.5, y >= 0.5 - MOI.empty!(model) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], x), 0.0), - ) - vc1 = MOI.add_constraint( - model, - MOI.SingleVariable(x[1]), - MOI.GreaterThan(0.5), - ) - # We test this after the creation of every `SingleVariable` constraint - # to ensure a good coverage of corner cases. - @test vc1.value == x[1].value - vc2 = MOI.add_constraint( - model, - MOI.SingleVariable(x[2]), - MOI.GreaterThan(0.5), - ) - @test vc2.value == x[2].value - MOI.add_constraint( - model, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm.([1.0], [x[2]]), # affine terms - MOI.ScalarQuadraticTerm.( - [2.0, 2.0], - [x[1], x[1]], - [x[1], x[1]], - ), # quad - 0.0, # constant - ), - MOI.LessThan(1.0), - ) - test_model_solution( - model, - config; - objective_value = 1.5, - variable_primal = [(x[1], 0.5), (x[2], 0.5)], - ) - end - @testset "Duplicate off-diagonal" begin - # max x + 2y | x^2 + 0.25y*x + 0.25x*y + 0.5x*y + y^2 <= 1, x >= 0.5, y >= 0.5 - MOI.empty!(model) - x = MOI.add_variables(model, 2) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], x), 0.0), - ) - vc1 = MOI.add_constraint( - model, - MOI.SingleVariable(x[1]), - MOI.GreaterThan{Float64}(0.5), - ) - @test vc1.value == x[1].value - vc2 = MOI.add_constraint( - model, - MOI.SingleVariable(x[2]), - MOI.GreaterThan{Float64}(0.5), - ) - @test vc2.value == x[2].value - MOI.add_constraint( - model, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm{Float64}[], # affine terms - MOI.ScalarQuadraticTerm.( - [2.0, 0.25, 0.25, 0.5, 2.0], - [x[1], x[1], x[2], x[1], x[2]], - [x[1], x[2], x[1], x[2], x[2]], - ), # quad - 0.0, # constant - ), - MOI.LessThan(1.0), - ) - test_model_solution( - model, - config; - objective_value = 0.5 + (√13 - 1) / 2, - variable_primal = [(x[1], 0.5), (x[2], (√13 - 1) / 4)], - ) + MOI.empty!(model) + x = MOI.add_variables(model, 2) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], x), 0.0), + ) + vc1 = MOI.add_constraint( + model, + MOI.SingleVariable(x[1]), + MOI.GreaterThan(0.5), + ) + # We test this after the creation of every `SingleVariable` constraint + # to ensure a good coverage of corner cases. + @test vc1.value == x[1].value + vc2 = MOI.add_constraint( + model, + MOI.SingleVariable(x[2]), + MOI.GreaterThan(0.5), + ) + @test vc2.value == x[2].value + MOI.add_constraint( + model, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm.([1.0], [x[2]]), # affine terms + MOI.ScalarQuadraticTerm.([2.0, 2.0], [x[1], x[1]], [x[1], x[1]]), # quad + 0.0, # constant + ), + MOI.LessThan(1.0), + ) + _test_model_solution( + model, + config; + objective_value = 1.5, + variable_primal = [(x[1], 0.5), (x[2], 0.5)], + ) + return +end + +function setup_test( + ::typeof(test_qcp_duplicate_diagonal), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5, 0.5]), + ), + ) + return +end + +""" + test_qcp_duplicate_off_diagonal(model::MOI.ModelLike, config::Config) + +Test a QCP problem with a duplicate off-diagonal term. + +The problem is +`max x + 2y | x^2 + 0.25y*x + 0.25x*y + 0.5x*y + y^2 <= 1, x >= 0.5, y >= 0.5`. +""" +function test_qcp_duplicate_off_diagonal(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint( + model, + MOI.ScalarQuadraticFunction{Float64}, + MOI.LessThan{Float64}, + ) + return end + MOI.empty!(model) + x = MOI.add_variables(model, 2) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], x), 0.0), + ) + vc1 = MOI.add_constraint( + model, + MOI.SingleVariable(x[1]), + MOI.GreaterThan{Float64}(0.5), + ) + @test vc1.value == x[1].value + vc2 = MOI.add_constraint( + model, + MOI.SingleVariable(x[2]), + MOI.GreaterThan{Float64}(0.5), + ) + @test vc2.value == x[2].value + MOI.add_constraint( + model, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm{Float64}[], # affine terms + MOI.ScalarQuadraticTerm.( + [2.0, 0.25, 0.25, 0.5, 2.0], + [x[1], x[1], x[2], x[1], x[2]], + [x[1], x[2], x[1], x[2], x[2]], + ), # quad + 0.0, # constant + ), + MOI.LessThan(1.0), + ) + _test_model_solution( + model, + config; + objective_value = 0.5 + (√13 - 1) / 2, + variable_primal = [(x[1], 0.5), (x[2], (√13 - 1) / 4)], + ) + return +end + +function setup_test( + ::typeof(test_qcp_duplicate_off_diagonal), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5, (√13 - 1) / 4]), + ), + ) + return end -unittests["solve_qcp_edge_cases"] = solve_qcp_edge_cases """ - solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) + test_modification_affine_deletion_edge_cases( + model::MOI.ModelLike, + config::Config, + ) Test various edge cases relating to deleting affine constraints. This requires + ScalarAffineFunction-in-LessThan; and @@ -370,7 +582,10 @@ Test various edge cases relating to deleting affine constraints. This requires If `config.solve=true` confirm that it solves correctly. """ -function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) +function test_modification_affine_deletion_edge_cases( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) x = MOI.add_variable(model) # helpers. The function 1.0x + 0.0 @@ -392,7 +607,7 @@ function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) ) # test adding a VectorAffineFunction -in- LessThan c1 = MOI.add_constraint(model, vaf, MOI.Nonpositives(1)) - test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, @@ -400,7 +615,7 @@ function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) ) # test adding a ScalarAffineFunction -in- LessThan c2 = MOI.add_constraint(model, saf, MOI.LessThan(1.0)) - test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, @@ -414,7 +629,7 @@ function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) catch err @test err.index == c1 end - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -422,7 +637,7 @@ function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) ) # add a different VectorAffineFunction constraint c3 = MOI.add_constraint(model, vaf2, MOI.Nonpositives(1)) - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -430,16 +645,45 @@ function solve_affine_deletion_edge_cases(model::MOI.ModelLike, config::Config) ) # delete the ScalarAffineFunction MOI.delete(model, c2) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, constraint_primal = [(c3, [0.0])], ) + return +end + +function setup_test( + ::typeof(test_modification_affine_deletion_edge_cases), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [2.0])), + ) + return end -unittests["solve_affine_deletion_edge_cases"] = solve_affine_deletion_edge_cases -function solve_zero_one_with_bounds_1(model::MOI.ModelLike, config::Config) +""" + test_constraint_ZeroOne_bounds(model::MOI.ModelLike, config::Config) + +Test a problem with a bounded ZeroOne variable. +""" +function test_constraint_ZeroOne_bounds(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -452,16 +696,37 @@ function solve_zero_one_with_bounds_1(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, variable_primal = [(x, 1.0)], ) + return end -unittests["solve_zero_one_with_bounds_1"] = solve_zero_one_with_bounds_1 -function solve_zero_one_with_bounds_2(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_constraint_ZeroOne_bounds), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return +end + +""" + test_constraint_ZeroOne_bounds_2(model::MOI.ModelLike, config::Config) + +Test a problem with a ZeroOne and binding fractional upper bound. +""" +function test_constraint_ZeroOne_bounds_2(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -474,16 +739,37 @@ function solve_zero_one_with_bounds_2(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, variable_primal = [(x, 0.0)], ) + return +end + +function setup_test( + ::typeof(test_constraint_ZeroOne_bounds_2), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])), + ) + return end -unittests["solve_zero_one_with_bounds_2"] = solve_zero_one_with_bounds_2 -function solve_zero_one_with_bounds_3(model::MOI.ModelLike, config::Config) +""" + test_constraint_ZeroOne_bounds_3(model::MOI.ModelLike, config::Config) + +Test a problem with a ZeroOne and infeasible fractional bounds. +""" +function test_constraint_ZeroOne_bounds_3(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) + return + end MOI.empty!(model) MOIU.loadfromstring!( model, @@ -500,23 +786,43 @@ function solve_zero_one_with_bounds_3(model::MOI.ModelLike, config::Config) MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE end + return +end + +function setup_test( + ::typeof(test_constraint_ZeroOne_bounds_3), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.INFEASIBLE), + ) + return end -unittests["solve_zero_one_with_bounds_3"] = solve_zero_one_with_bounds_3 """ - solve_start_soc(model::MOI.ModelLike, config::Config{T}) where {T} + test_constraint_PrimalStart_DualStart_SecondOrderCone( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} Test combining the [`MOI.VariablePrimalStart`](@ref), [`MOI.ConstraintPrimalStart`](@ref) and [`MOI.ConstraintDualStart`](@ref) attributes with a `MOI.VectorAffineFunction{T}`-in-`MOI.SecondOrderCone`. """ -function solve_start_soc(model::MOI.ModelLike, config::Config{T}) where {T} +function test_constraint_PrimalStart_DualStart_SecondOrderCone( + model::MOI.ModelLike, + config::Config{T}, +) where {T} if !MOI.supports_constraint( model, MOI.VectorAffineFunction{T}, MOI.SecondOrderCone, ) return + elseif !config.solve + return end MOI.empty!(model) x = MOI.add_variable(model) @@ -538,17 +844,33 @@ function solve_start_soc(model::MOI.ModelLike, config::Config{T}) where {T} if MOI.supports(model, MOI.ConstraintDualStart(), typeof(c)) MOI.set(model, MOI.ConstraintDualStart(), c, T[2, -2]) end - if config.solve - MOI.optimize!(model) - MOI.Test.test_model_solution( - model, - config; - objective_value = o, - variable_primal = [(x, o)], - constraint_primal = [(c, [o, o])], - constraint_dual = [(c, [o, -o])], - ) - end + MOI.optimize!(model) + MOI.Test._test_model_solution( + model, + config; + objective_value = o, + variable_primal = [(x, o)], + constraint_primal = [(c, [o, o])], + constraint_dual = [(c, [o, -o])], + ) + return +end + +function setup_test( + ::typeof(test_constraint_PrimalStart_DualStart_SecondOrderCone), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => + [[1.0, -1.0]], + ), + ) return end -unittests["solve_start_soc"] = solve_start_soc diff --git a/src/Test/UnitTests/modifications.jl b/src/Test/UnitTests/modifications.jl index 7a12f2dd29..71e32a5f18 100644 --- a/src/Test/UnitTests/modifications.jl +++ b/src/Test/UnitTests/modifications.jl @@ -1,12 +1,16 @@ -const modificationtests = Dict{String,Function}() - """ - set_function_single_variable(model::MOI.ModelLike, config::Config) + test_modification_set_function_single_variable( + model::MOI.ModelLike, + config::Config, + ) Test that modifying the function of a `SingleVariable`-in-`LessThan` constraint throws a [`SettingSingleVariableFunctionNotAllowed`](@ref) error. """ -function set_function_single_variable(model::MOI.ModelLike, config::Config) +function test_modification_set_function_single_variable( + model::MOI.ModelLike, + ::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -25,17 +29,23 @@ function set_function_single_variable(model::MOI.ModelLike, config::Config) err = MOI.SettingSingleVariableFunctionNotAllowed() func = MOI.SingleVariable(y) @test_throws err MOI.set(model, MOI.ConstraintFunction(), c, func) + return end -modificationtests["set_function_single_variable"] = set_function_single_variable """ - solve_set_singlevariable_lessthan(model::MOI.ModelLike, config::Config) + test_modification_set_singlevariable_lessthan( + model::MOI.ModelLike, + config::Config, + ) Test set modification SingleVariable-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_set_singlevariable_lessthan(model::MOI.ModelLike, config::Config) +function test_modification_set_singlevariable_lessthan( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -48,7 +58,7 @@ function solve_set_singlevariable_lessthan(model::MOI.ModelLike, config::Config) x = MOI.get(model, MOI.VariableIndex, "x") c = MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}(x.value) @test c.value == x.value - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -58,7 +68,7 @@ function solve_set_singlevariable_lessthan(model::MOI.ModelLike, config::Config) ) MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(2.0)) @test MOI.get(model, MOI.ConstraintSet(), c) == MOI.LessThan(2.0) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, @@ -66,18 +76,43 @@ function solve_set_singlevariable_lessthan(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 2.0)], constraint_dual = [(c, -1.0)], ) + return +end + +function setup_test( + ::typeof(test_modification_set_singlevariable_lessthan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [2.0]), + MOI.FEASIBLE_POINT, + ), + ) + return end -modificationtests["solve_set_singlevariable_lessthan"] = - solve_set_singlevariable_lessthan """ - solve_transform_singlevariable_lessthan(model::MOI.ModelLike, config::Config) + test_modification_transform_singlevariable_lessthan( + model::MOI.ModelLike, + config::Config, + ) Test set transformation of a SingleVariable-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_transform_singlevariable_lessthan( +function test_modification_transform_singlevariable_lessthan( model::MOI.ModelLike, config::Config, ) @@ -93,7 +128,7 @@ function solve_transform_singlevariable_lessthan( x = MOI.get(model, MOI.VariableIndex, "x") c = MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}(x.value) @test c.value == x.value - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -105,7 +140,7 @@ function solve_transform_singlevariable_lessthan( @test !MOI.is_valid(model, c) @test MOI.is_valid(model, c2) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, @@ -113,18 +148,46 @@ function solve_transform_singlevariable_lessthan( constraint_primal = [(c2, 2.0)], constraint_dual = [(c2, 1.0)], ) + return +end + +function setup_test( + ::typeof(test_modification_transform_singlevariable_lessthan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [2.0]), + MOI.FEASIBLE_POINT, + ), + ) + return end -modificationtests["solve_transform_singlevariable_lessthan"] = - solve_transform_singlevariable_lessthan """ - solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) + test_modification_set_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, + ) Test modifying set of ScalarAffineFunction-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) +function test_modification_set_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -136,7 +199,7 @@ function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) ) x = MOI.get(model, MOI.VariableIndex, "x") c = MOI.get(model, MOI.ConstraintIndex, "c") - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -146,7 +209,7 @@ function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) ) MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(2.0)) @test MOI.get(model, MOI.ConstraintSet(), c) == MOI.LessThan(2.0) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, @@ -154,18 +217,50 @@ function solve_set_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 2.0)], constraint_dual = [(c, -1.0)], ) + return +end + +function setup_test( + ::typeof(test_modification_set_scalaraffine_lessthan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [2.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + ) + return end -modificationtests["solve_set_scalaraffine_lessthan"] = - solve_set_scalaraffine_lessthan """ - solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) + test_modification_coef_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, + ) Test modifying a variable coefficient in a ScalarAffineFunction-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) +function test_modification_coef_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -177,7 +272,7 @@ function solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) ) x = MOI.get(model, MOI.VariableIndex, "x") c = MOI.get(model, MOI.ConstraintIndex, "c") - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -186,7 +281,7 @@ function solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) constraint_dual = [(c, -1.0)], ) MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 2.0)) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -194,18 +289,50 @@ function solve_coef_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, -0.5)], ) + return +end + +function setup_test( + ::typeof(test_modification_coef_scalaraffine_lessthan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-0.5], + ), + ) + return end -modificationtests["solve_coef_scalaraffine_lessthan"] = - solve_coef_scalaraffine_lessthan """ - solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) + test_modification_func_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, + ) Test setting the function in a ScalarAffineFunction-in-LessThan constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) +function test_modification_func_scalaraffine_lessthan( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -217,7 +344,7 @@ function solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) ) x = MOI.get(model, MOI.VariableIndex, "x") c = MOI.get(model, MOI.ConstraintIndex, "c") - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -233,7 +360,7 @@ function solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) ) foo = MOI.get(model, MOI.ConstraintFunction(), c) @test foo ≈ MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 0.0) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -241,18 +368,50 @@ function solve_func_scalaraffine_lessthan(model::MOI.ModelLike, config::Config) # constraint_primal = [(c, 1.0)], constraint_dual = [(c, -0.5)], ) + return +end + +function setup_test( + ::typeof(test_modification_func_scalaraffine_lessthan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-0.5], + ), + ) + return end -modificationtests["solve_func_scalaraffine_lessthan"] = - solve_func_scalaraffine_lessthan """ - solve_func_vectoraffine_nonneg(model::MOI.ModelLike, config::Config) + test_modification_func_vectoraffine_nonneg( + model::MOI.ModelLike, + config::Config, + ) Test setting the function in a VectorAffineFunction-in-Nonnegatives constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_func_vectoraffine_nonneg(model::MOI.ModelLike, config::Config) +function test_modification_func_vectoraffine_nonneg( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -265,7 +424,7 @@ function solve_func_vectoraffine_nonneg(model::MOI.ModelLike, config::Config) x = MOI.get(model, MOI.VariableIndex, "x") y = MOI.get(model, MOI.VariableIndex, "y") c = MOI.get(model, MOI.ConstraintIndex, "c") - test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, @@ -292,25 +451,51 @@ function solve_func_vectoraffine_nonneg(model::MOI.ModelLike, config::Config) ], [-1.0, -1.5], ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.5, variable_primal = [(x, 1.0), (y, 0.75)], constraint_primal = [(c, [0.0, 0.0])], ) + return +end + +function setup_test( + ::typeof(test_modification_func_vectoraffine_nonneg), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.0, 0.0]), + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 0.75]), + ), + ) + return end -modificationtests["solve_func_vectoraffine_nonneg"] = - solve_func_vectoraffine_nonneg """ - solve_const_vectoraffine_nonpos(model::MOI.ModelLike, config::Config) + test_modification_const_vectoraffine_nonpos( + model::MOI.ModelLike, + config::Config, + ) Test modifying the constant term in a VectorAffineFunction-in-Nonpositives constraint. If `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_const_vectoraffine_nonpos(model::MOI.ModelLike, config::Config) +function test_modification_const_vectoraffine_nonpos( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -332,7 +517,7 @@ function solve_const_vectoraffine_nonpos(model::MOI.ModelLike, config::Config) ), MOI.Nonpositives(2), ) - test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, @@ -340,25 +525,48 @@ function solve_const_vectoraffine_nonpos(model::MOI.ModelLike, config::Config) constraint_primal = [(c, [0.0, 0.0])], ) MOI.modify(model, c, MOI.VectorConstantChange([-1.0, -1.5])) - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.5, variable_primal = [(x, 1.0), (y, 0.75)], constraint_primal = [(c, [0.0, 0.0])], ) + return +end + +function setup_test( + ::typeof(test_modification_const_vectoraffine_nonpos), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.0, 0.0]), + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 0.75]), + ), + ) + return end -modificationtests["solve_const_vectoraffine_nonpos"] = - solve_const_vectoraffine_nonpos """ - solve_multirow_vectoraffine_nonpos(model::MOI.ModelLike, config::Config) + test_modification_multirow_vectoraffine_nonpos( + model::MOI.ModelLike, + config::Config, + ) Test modifying the variable coefficients in a VectorAffineFunction-in-Nonpositives constraint. If `config.solve=true` confirm that it solves correctly. """ -function solve_multirow_vectoraffine_nonpos( +function test_modification_multirow_vectoraffine_nonpos( model::MOI.ModelLike, config::Config, ) @@ -382,7 +590,7 @@ function solve_multirow_vectoraffine_nonpos( ), MOI.Nonpositives(2), ) - test_model_solution( + _test_model_solution( model, config; objective_value = 0.5, @@ -390,24 +598,47 @@ function solve_multirow_vectoraffine_nonpos( constraint_primal = [(c, [-0.5, 0.0])], ) MOI.modify(model, c, MOI.MultirowChange(x, [(1, 4.0), (2, 3.0)])) - return test_model_solution( + _test_model_solution( model, config; objective_value = 0.25, variable_primal = [(x, 0.25)], constraint_primal = [(c, [0.0, -0.25])], ) + return +end + +function setup_test( + ::typeof(test_modification_multirow_vectoraffine_nonpos), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5])), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [0.25]), + ), + ) + return end -modificationtests["solve_multirow_vectoraffine_nonpos"] = - solve_multirow_vectoraffine_nonpos """ - solve_const_scalar_objective(model::MOI.ModelLike, config::Config) + test_modification_const_scalar_objective( + model::MOI.ModelLike, + config::Config, + ) Test the constant of a scalaraffine objective. If `config.solve=true` confirm that it solves correctly. """ -function solve_const_scalar_objective(model::MOI.ModelLike, config::Config) +function test_modification_const_scalar_objective( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -418,7 +649,7 @@ function solve_const_scalar_objective(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, @@ -429,22 +660,43 @@ function solve_const_scalar_objective(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarConstantChange(3.0), ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 4.0, variable_primal = [(x, 1.0)], ) + return +end + +function setup_test( + ::typeof(test_modification_const_scalar_objective), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return end -modificationtests["solve_const_scalar_objective"] = solve_const_scalar_objective """ - solve_coef_scalar_objective(model::MOI.ModelLike, config::Config) + test_modification_coef_scalar_objective( + model::MOI.ModelLike, + config::Config + ) Test modifying a variable coefficient in a scalaraffine objective. If `config.solve=true` confirm that it solves correctly. """ -function solve_coef_scalar_objective(model::MOI.ModelLike, config::Config) +function test_modification_coef_scalar_objective( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -455,7 +707,7 @@ function solve_coef_scalar_objective(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -466,20 +718,42 @@ function solve_coef_scalar_objective(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarCoefficientChange(x, 3.0), ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, variable_primal = [(x, 1.0)], ) + return end -modificationtests["solve_coef_scalar_objective"] = solve_coef_scalar_objective -function delete_variable_with_single_variable_obj( +function setup_test( + ::typeof(test_modification_coef_scalar_objective), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return +end + +""" + test_modification_delete_variable_with_single_variable_obj( + model::MOI.ModelLike, + config::Config, + ) + +Test deleting a variable that is the objective function. +""" +function test_modification_delete_variable_with_single_variable_obj( model::MOI.ModelLike, config::Config, ) - atol, rtol = config.atol, config.rtol MOI.empty!(model) @test MOI.is_empty(model) MOIU.loadfromstring!( @@ -497,7 +771,7 @@ function delete_variable_with_single_variable_obj( ) @test c.value == x.value MOI.delete(model, y) - return test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -505,18 +779,41 @@ function delete_variable_with_single_variable_obj( constraint_primal = [(c, 1.0)], constraint_dual = [(c, 1.0)], ) + return +end + +function setup_test( + ::typeof(test_modification_delete_variable_with_single_variable_obj), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], + ), + ) + return end -modificationtests["delete_variable_with_single_variable_obj"] = - delete_variable_with_single_variable_obj """ - delete_variables_in_a_batch(model::MOI.ModelLike, config::Config) + test_modification_delete_variables_in_a_batch( + model::MOI.ModelLike, + config::Config, + ) Test deleting many variables in a batch (i.e. using the delete method which takes a vector of variable references). If `config.solve=true` confirm that it solves correctly. """ -function delete_variables_in_a_batch(model::MOI.ModelLike, config::Config) +function test_modification_delete_variables_in_a_batch( + model::MOI.ModelLike, + config::Config, +) atol = config.atol rtol = config.rtol MOI.empty!(model) @@ -547,7 +844,28 @@ function delete_variables_in_a_batch(model::MOI.ModelLike, config::Config) MOI.optimize!(model) @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2.0 atol = atol rtol = rtol end + return end -modificationtests["delete_variables_in_a_batch"] = delete_variables_in_a_batch -@moitestset modification +function setup_test( + ::typeof(test_modification_delete_variables_in_a_batch), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 1.0, 1.0]), + MOI.FEASIBLE_POINT, + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + ), + ) + return +end diff --git a/src/Test/UnitTests/objectives.jl b/src/Test/UnitTests/objectives.jl index e2b57ac35e..2064fc8bec 100644 --- a/src/Test/UnitTests/objectives.jl +++ b/src/Test/UnitTests/objectives.jl @@ -1,67 +1,50 @@ -#= - Functions in this file test functionality relating to objectives in MOI. - -### Requires - - optimize! - -### Functionality currently tested - - get/set ObjectiveSense - - a constant in a affine objective - - a blank objective - -### Functionality not yet tested - - Quadratic Objectives - - Modifications -=# - """ - max_sense(model::MOI.ModelLike, config::Config) + test_ObjectiveSense_MAX_SENSE(model::MOI.ModelLike, config::Config) Test setting objective sense to MAX_SENSE. """ -function max_sense(model::MOI.ModelLike, config::Config) +function test_ObjectiveSense_MAX_SENSE(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports(model, MOI.ObjectiveSense()) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + return end -unittests["max_sense"] = max_sense """ - min_sense(model::MOI.ModelLike, config::Config) + test_ObjectiveSense_MIN_SENSE(model::MOI.ModelLike, config::Config) Test setting objective sense to MIN_SENSE. """ -function min_sense(model::MOI.ModelLike, config::Config) +function test_ObjectiveSense_MIN_SENSE(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports(model, MOI.ObjectiveSense()) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE + return end -unittests["min_sense"] = min_sense """ - feasibility_sense(model::MOI.ModelLike, config::Config) + test_ObjectiveSense_FEASIBILITY_SENSE(model::MOI.ModelLike, config::Config) Test setting objective sense to FEASIBILITY_SENSE. """ -function feasibility_sense(model::MOI.ModelLike, config::Config) +function test_ObjectiveSense_FEASIBILITY_SENSE(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports(model, MOI.ObjectiveSense()) MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE end -unittests["feasibility_sense"] = feasibility_sense """ - get_objective_function(model::MOI.ModelLike, config::Config) + test_get_ObjectiveFunction(model::MOI.ModelLike, config::Config) Test get objective function. """ -function get_objective_function(model::MOI.ModelLike, config::Config) +function test_get_ObjectiveFunction(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) obj_attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}() @@ -76,9 +59,10 @@ function get_objective_function(model::MOI.ModelLike, config::Config) x = MOI.get(model, MOI.VariableIndex, "x") expected_obj_fun = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 1.0) - @test_throws InexactError begin - MOI.get(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) - end + @test_throws( + InexactError, + MOI.get(model, MOI.ObjectiveFunction{MOI.SingleVariable}()), + ) obj_fun = MOI.get(model, obj_attr) @test obj_fun ≈ expected_obj_fun quad_obj_attr = @@ -86,18 +70,17 @@ function get_objective_function(model::MOI.ModelLike, config::Config) quad_obj_fun = MOI.get(model, quad_obj_attr) @test convert(MOI.ScalarAffineFunction{Float64}, quad_obj_fun) ≈ expected_obj_fun + return end -unittests["get_objective_function"] = get_objective_function """ - solve_constant_obj(model::MOI.ModelLike, config::Config) + test_ObjectiveFunction_constant(model::MOI.ModelLike, config::Config) Test constant in linear objective, if `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_constant_obj(model::MOI.ModelLike, config::Config) - atol, rtol = config.atol, config.rtol +function test_ObjectiveFunction_constant(model::MOI.ModelLike, config::Config) MOI.empty!(model) @test MOI.is_empty(model) MOIU.loadfromstring!( @@ -115,7 +98,7 @@ function solve_constant_obj(model::MOI.ModelLike, config::Config) # We test this after the creation of every `SingleVariable` constraint # to ensure a good coverage of corner cases. @test c.value == x.value - return test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, @@ -123,17 +106,37 @@ function solve_constant_obj(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, 2.0)], ) + return +end + +function setup_test( + ::typeof(test_ObjectiveFunction_constant), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1]), + MOI.FEASIBLE_POINT, + ), + ) + return end -unittests["solve_constant_obj"] = solve_constant_obj """ - solve_blank_obj(model::MOI.ModelLike, config::Config) + test_ObjectiveFunction_blank(model::MOI.ModelLike, config::Config) Test blank linear objective, if `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_blank_obj(model::MOI.ModelLike, config::Config) +function test_ObjectiveFunction_blank(model::MOI.ModelLike, config::Config) + if !config.solve + return + end atol, rtol = config.atol, config.rtol MOI.empty!(model) @test MOI.is_empty(model) @@ -150,30 +153,47 @@ function solve_blank_obj(model::MOI.ModelLike, config::Config) x.value, ) @test c.value == x.value - test_model_solution( + _test_model_solution( model, config; objective_value = 0.0, constraint_dual = [(c, 0.0)], ) - if config.solve - # The objective is blank so any primal value ≥ 1 is correct - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.VariablePrimal(), x) + atol + rtol ≥ 1.0 - @test MOI.get(model, MOI.ConstraintPrimal(), c) + atol + rtol ≥ 1.0 - end + # The objective is blank so any primal value ≥ 1 is correct + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.VariablePrimal(), x) + atol + rtol >= 1.0 + @test MOI.get(model, MOI.ConstraintPrimal(), c) + atol + rtol >= 1.0 + return +end + +function setup_test( + ::typeof(test_ObjectiveFunction_blank), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.2]), + MOI.FEASIBLE_POINT, + ), + ) + return end -unittests["solve_blank_obj"] = solve_blank_obj """ - solve_singlevariable_obj(model::MOI.ModelLike, config::Config) + test_ObjectiveFunction_SingleVariable(model::MOI.ModelLike, config::Config) Test SingleVariable objective, if `config.solve=true` confirm that it solves correctly, and if `config.duals=true`, check that the duals are computed correctly. """ -function solve_singlevariable_obj(model::MOI.ModelLike, config::Config) - atol, rtol = config.atol, config.rtol +function test_ObjectiveFunction_SingleVariable( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) @test MOI.is_empty(model) MOIU.loadfromstring!( @@ -189,7 +209,7 @@ function solve_singlevariable_obj(model::MOI.ModelLike, config::Config) x.value, ) @test c.value == x.value - return test_model_solution( + _test_model_solution( model, config; objective_value = 1.0, @@ -197,20 +217,42 @@ function solve_singlevariable_obj(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, 1.0)], ) + return +end + +function setup_test( + ::typeof(test_ObjectiveFunction_SingleVariable), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1]), + MOI.FEASIBLE_POINT, + ), + ) + return end -unittests["solve_singlevariable_obj"] = solve_singlevariable_obj """ - solve_qp_edge_cases(model::MOI.ModelLike, config::Config) + test_qp_ObjectiveFunction_edge_cases(model::MOI.ModelLike, config::Config) Test various edge cases relating to quadratic programs (i.e., with a quadratic objective function). If `config.solve=true` confirm that it solves correctly. """ -function solve_qp_edge_cases(model::MOI.ModelLike, config::Config) +function test_qp_ObjectiveFunction_edge_cases( + model::MOI.ModelLike, + config::Config, +) obj_attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}() - MOI.supports(model, obj_attr) || return + if !MOI.supports(model, obj_attr) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -226,99 +268,127 @@ function solve_qp_edge_cases(model::MOI.ModelLike, config::Config) MOI.GreaterThan(2.0), ) @test vc2.value == x[2].value - @testset "Basic model" begin - # min x^2 + y^2 | x>=1, y>=2 - MOI.set( - model, - obj_attr, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm{Float64}[], # affine terms - MOI.ScalarQuadraticTerm.([2.0, 2.0], x, x), # quad - 0.0, # constant - ), - ) - test_model_solution( - model, - config; - objective_value = 5.0, - variable_primal = [(x[1], 1.0), (x[2], 2.0)], - ) - end - @testset "Duplicate linear terms" begin - # min x + x + x^2 + y^2 | x>=1, y>=2 - MOI.set( - model, - obj_attr, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm.([1.0, 1.0], [x[1], x[1]]), # affine terms - MOI.ScalarQuadraticTerm.([2.0, 2.0], x, x), # quad - 0.0, # constant - ), - ) - test_model_solution( - model, - config; - objective_value = 7.0, - variable_primal = [(x[1], 1.0), (x[2], 2.0)], - ) - end - @testset "Duplicate diagonal terms" begin - # min x^2 + x^2 | x>=1, y>=2 - MOI.set( - model, - obj_attr, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm{Float64}[], # affine terms - MOI.ScalarQuadraticTerm.( - [2.0, 2.0], - [x[1], x[1]], - [x[1], x[1]], - ), # quad - 0.0, # constant - ), - ) - test_model_solution( - model, - config; - objective_value = 2.0, - variable_primal = [(x[1], 1.0)], - ) - end - @testset "Duplicate off-diagonal terms" begin - # min x^2 + 0.25x*y + 0.25y*x + 0.5x*y + y^2 | x>=1, y>=2 - MOI.set( - model, - obj_attr, - MOI.ScalarQuadraticFunction( - MOI.ScalarAffineTerm{Float64}[], # affine terms - MOI.ScalarQuadraticTerm.( - [2.0, 0.25, 0.25, 0.5, 2.0], - [x[1], x[1], x[2], x[1], x[2]], - [x[1], x[2], x[1], x[2], x[2]], - ), # quad - 0.0, # constant - ), - ) - test_model_solution( - model, - config; - objective_value = 7.0, - variable_primal = [(x[1], 1.0), (x[2], 2.0)], - ) - end + # Basic model + # min x^2 + y^2 | x>=1, y>=2 + MOI.set( + model, + obj_attr, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm{Float64}[], # affine terms + MOI.ScalarQuadraticTerm.([2.0, 2.0], x, x), # quad + 0.0, # constant + ), + ) + _test_model_solution( + model, + config; + objective_value = 5.0, + variable_primal = [(x[1], 1.0), (x[2], 2.0)], + ) + # Duplicate linear terms + # min x + x + x^2 + y^2 | x>=1, y>=2 + MOI.set( + model, + obj_attr, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm.([1.0, 1.0], [x[1], x[1]]), # affine terms + MOI.ScalarQuadraticTerm.([2.0, 2.0], x, x), # quad + 0.0, # constant + ), + ) + _test_model_solution( + model, + config; + objective_value = 7.0, + variable_primal = [(x[1], 1.0), (x[2], 2.0)], + ) + # Duplicate diagonal terms + # min x^2 + x^2 | x>=1, y>=2 + MOI.set( + model, + obj_attr, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm{Float64}[], # affine terms + MOI.ScalarQuadraticTerm.([2.0, 2.0], [x[1], x[1]], [x[1], x[1]]), # quad + 0.0, # constant + ), + ) + _test_model_solution( + model, + config; + objective_value = 2.0, + variable_primal = [(x[1], 1.0)], + ) + # Duplicate off-diagonal terms" begin + # min x^2 + 0.25x*y + 0.25y*x + 0.5x*y + y^2 | x>=1, y>=2 + MOI.set( + model, + obj_attr, + MOI.ScalarQuadraticFunction( + MOI.ScalarAffineTerm{Float64}[], # affine terms + MOI.ScalarQuadraticTerm.( + [2.0, 0.25, 0.25, 0.5, 2.0], + [x[1], x[1], x[2], x[1], x[2]], + [x[1], x[2], x[1], x[2], x[2]], + ), # quad + 0.0, # constant + ), + ) + _test_model_solution( + model, + config; + objective_value = 7.0, + variable_primal = [(x[1], 1.0), (x[2], 2.0)], + ) + return +end + +function setup_test( + ::typeof(test_qp_ObjectiveFunction_edge_cases), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]), + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]), + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]), + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]), + ), + ) + return end -unittests["solve_qp_edge_cases"] = solve_qp_edge_cases """ - solve_qp_zero_offdiag(model::MOI.ModelLike, config::Config) + test_qp_ObjectiveFunction_zero_ofdiag(model::MOI.ModelLike, config::Config) Test quadratic program with a zero off-diagonal term. If `config.solve=true` confirm that it solves correctly. """ -function solve_qp_zero_offdiag(model::MOI.ModelLike, config::Config) +function test_qp_ObjectiveFunction_zero_ofdiag( + model::MOI.ModelLike, + config::Config, +) obj_attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}() - MOI.supports(model, obj_attr) || return + if !MOI.supports(model, obj_attr) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -347,22 +417,41 @@ function solve_qp_zero_offdiag(model::MOI.ModelLike, config::Config) 0.0, # constant ), ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 5.0, variable_primal = [(x[1], 1.0), (x[2], 2.0)], ) + return +end + +function setup_test( + ::typeof(test_qp_ObjectiveFunction_zero_ofdiag), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0, 2.0]), + ), + ) + return end -unittests["solve_qp_zero_offdiag"] = solve_qp_zero_offdiag """ - solve_duplicate_terms_obj(model::MOI.ModelLike, config::Config) + test_ObjectiveFunction_duplicate_terms(model::MOI.ModelLike, config::Config) Test duplicate terms in linear objective, if `config.solve=true` confirm that it solves correctly. """ -function solve_duplicate_terms_obj(model::MOI.ModelLike, config::Config) +function test_ObjectiveFunction_duplicate_terms( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) @test MOI.is_empty(model) x = MOI.add_variable(model) @@ -377,7 +466,7 @@ function solve_duplicate_terms_obj(model::MOI.ModelLike, config::Config) 0.0, ), ) - return test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, @@ -385,5 +474,22 @@ function solve_duplicate_terms_obj(model::MOI.ModelLike, config::Config) constraint_primal = [(c, 1.0)], constraint_dual = [(c, 3.0)], ) + return +end + +function setup_test( + ::typeof(test_ObjectiveFunction_duplicate_terms), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1]), + MOI.FEASIBLE_POINT, + ), + ) + return end -unittests["solve_duplicate_terms_obj"] = solve_duplicate_terms_obj diff --git a/src/Test/UnitTests/solve.jl b/src/Test/UnitTests/solve.jl index 2374ee0642..03552c6ef5 100644 --- a/src/Test/UnitTests/solve.jl +++ b/src/Test/UnitTests/solve.jl @@ -1,9 +1,12 @@ """ - solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) + test_ObjectiveBound_edge_cases(model::MOI.ModelLike, config::Config) Test a variety of edge cases related to the ObjectiveBound attribute. """ -function solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) +function test_ObjectiveBound_edge_cases(model::MOI.ModelLike, config::Config) + if !config.solve + return + end @testset "Min IP with constant" begin MOI.empty!(model) @test MOI.is_empty(model) @@ -17,15 +20,13 @@ function solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, variable_primal = [(x, 2.0)], ) - if config.solve - @test MOI.get(model, MOI.ObjectiveBound()) <= 3.0 - end + @test MOI.get(model, MOI.ObjectiveBound()) <= 3.0 end @testset "Max IP with constant" begin MOI.empty!(model) @@ -40,15 +41,13 @@ function solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 3.0, variable_primal = [(x, 1.0)], ) - if config.solve - @test MOI.get(model, MOI.ObjectiveBound()) >= 3.0 - end + @test MOI.get(model, MOI.ObjectiveBound()) >= 3.0 end @testset "Min LP with constant" begin MOI.empty!(model) @@ -62,15 +61,13 @@ function solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, variable_primal = [(x, 1.5)], ) - if config.solve - @test MOI.get(model, MOI.ObjectiveBound()) <= 2.0 - end + @test MOI.get(model, MOI.ObjectiveBound()) <= 2.0 end @testset "Max LP with constant" begin MOI.empty!(model) @@ -84,20 +81,58 @@ function solve_objbound_edge_cases(model::MOI.ModelLike, config::Config) """, ) x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( + _test_model_solution( model, config; objective_value = 4.0, variable_primal = [(x, 1.5)], ) - if config.solve - @test MOI.get(model, MOI.ObjectiveBound()) >= 4.0 - end + @test MOI.get(model, MOI.ObjectiveBound()) >= 4.0 end end -unittests["solve_objbound_edge_cases"] = solve_objbound_edge_cases -function solve_unbounded_model(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_ObjectiveBound_edge_cases), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 3.0) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [2.0])) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 3.0) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.0) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.5])) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 4.0) + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.5])) + end, + ) + return +end + +""" + test_TerminationStatus_DUAL_INFEASIBLE( + model::MOI.ModelLike, + config::Config, + ) + +Test an unbounded linear program. +""" +function test_TerminationStatus_DUAL_INFEASIBLE( + model::MOI.ModelLike, + config::Config, +) + if !config.solve + return + end MOI.empty!(model) x = MOI.add_variables(model, 5) MOI.set( @@ -106,14 +141,38 @@ function solve_unbounded_model(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), ) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - if config.solve - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE +end + +function setup_test( + ::typeof(test_TerminationStatus_DUAL_INFEASIBLE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE), + ) + return end -unittests["solve_unbounded_model"] = solve_unbounded_model -function solve_single_variable_dual_min(model::MOI.ModelLike, config::Config) +""" + test_SingleVariable_ConstraintDual_MIN_SENSE( + model::MOI.ModelLike, + config::Config, + ) + +Test `ConstraintDual` of a `SingleVariable` constraint when minimizing. +""" +function test_SingleVariable_ConstraintDual_MIN_SENSE( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.duals) + return + end MOI.empty!(model) x = MOI.add_variable(model) xl = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) @@ -124,23 +183,56 @@ function solve_single_variable_dual_min(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - if config.solve && config.duals - MOI.optimize!(model) - @test isapprox( - MOI.get(model, MOI.VariablePrimal(), x), - 1.0, - atol = config.atol, - ) - sl = MOI.get(model, MOI.ConstraintDual(), xl) - su = MOI.get(model, MOI.ConstraintDual(), xu) - @test isapprox(sl + su, 1.0, atol = config.atol) - @test sl >= -config.atol - @test su <= config.atol - end + MOI.optimize!(model) + @test isapprox( + MOI.get(model, MOI.VariablePrimal(), x), + 1.0, + atol = config.atol, + ) + sl = MOI.get(model, MOI.ConstraintDual(), xl) + su = MOI.get(model, MOI.ConstraintDual(), xu) + @test isapprox(sl + su, 1.0, atol = config.atol) + @test sl >= -config.atol + @test su <= config.atol + return end -unittests["solve_single_variable_dual_min"] = solve_single_variable_dual_min -function solve_single_variable_dual_max(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_SingleVariable_ConstraintDual_MIN_SENSE), + model::MOIU.MockOptimizer, + ::Config, +) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], + (MOI.SingleVariable, MOI.LessThan{Float64}) => [0.0], + ), + ) + return () -> model.eval_variable_constraint_dual = flag +end + +""" + test_SingleVariable_ConstraintDual_MAX_SENSE( + model::MOI.ModelLike, + config::Config, + ) + +Test `ConstraintDual` of a `SingleVariable` constraint when maximizing. +""" +function test_SingleVariable_ConstraintDual_MAX_SENSE( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.duals) + return + end MOI.empty!(model) x = MOI.add_variable(model) xl = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) @@ -151,23 +243,50 @@ function solve_single_variable_dual_max(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - if config.solve && config.duals - MOI.optimize!(model) - @test isapprox( - MOI.get(model, MOI.VariablePrimal(), x), - 1.0, - atol = config.atol, - ) - sl = MOI.get(model, MOI.ConstraintDual(), xl) - su = MOI.get(model, MOI.ConstraintDual(), xu) - @test isapprox(sl + su, -1.0, atol = config.atol) - @test sl >= -config.atol - @test su <= config.atol - end + MOI.optimize!(model) + @test isapprox( + MOI.get(model, MOI.VariablePrimal(), x), + 1.0, + atol = config.atol, + ) + sl = MOI.get(model, MOI.ConstraintDual(), xl) + su = MOI.get(model, MOI.ConstraintDual(), xu) + @test isapprox(sl + su, -1.0, atol = config.atol) + @test sl >= -config.atol + @test su <= config.atol + return end -unittests["solve_single_variable_dual_max"] = solve_single_variable_dual_max -function solve_result_index(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_SingleVariable_ConstraintDual_MAX_SENSE), + model::MOIU.MockOptimizer, + ::Config, +) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0], + (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1.0], + ), + ) + return () -> model.eval_variable_constraint_dual = flag +end + +""" + test_result_index(model::MOI.ModelLike, config::Config) + +Test that various attributess implement `.result_index` correctly. +""" +function test_result_index(model::MOI.ModelLike, config::Config) + if !config.solve + return + end atol = config.atol rtol = config.rtol MOI.empty!(model) @@ -179,60 +298,87 @@ function solve_result_index(model::MOI.ModelLike, config::Config) MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - if config.solve - MOI.optimize!(model) - result_count = MOI.get(model, MOI.ResultCount()) - function result_err(attr) - return MOI.ResultIndexBoundsError{typeof(attr)}(attr, result_count) - end - result_index = result_count + 1 - @test MOI.get(model, MOI.ObjectiveValue(1)) ≈ 1.0 atol = atol rtol = - rtol - @test_throws result_err(MOI.ObjectiveValue(result_index)) MOI.get( - model, - MOI.ObjectiveValue(result_index), - ) - if config.dual_objective_value - @test MOI.get(model, MOI.DualObjectiveValue(1)) ≈ 1.0 atol = atol rtol = - rtol - @test_throws result_err(MOI.DualObjectiveValue(result_index)) MOI.get( - model, - MOI.DualObjectiveValue(result_index), - ) - end - @test MOI.get(model, MOI.PrimalStatus(1)) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.PrimalStatus(result_index)) == MOI.NO_SOLUTION - @test MOI.get(model, MOI.VariablePrimal(1), x) ≈ 1.0 atol = atol rtol = + MOI.optimize!(model) + result_count = MOI.get(model, MOI.ResultCount()) + function result_err(attr) + return MOI.ResultIndexBoundsError{typeof(attr)}(attr, result_count) + end + result_index = result_count + 1 + @test MOI.get(model, MOI.ObjectiveValue(1)) ≈ 1.0 atol = atol rtol = rtol + @test_throws result_err(MOI.ObjectiveValue(result_index)) MOI.get( + model, + MOI.ObjectiveValue(result_index), + ) + if config.dual_objective_value + @test MOI.get(model, MOI.DualObjectiveValue(1)) ≈ 1.0 atol = atol rtol = rtol - @test_throws result_err(MOI.VariablePrimal(result_index)) MOI.get( + @test_throws result_err(MOI.DualObjectiveValue(result_index)) MOI.get( model, - MOI.VariablePrimal(result_index), - x, + MOI.DualObjectiveValue(result_index), ) - @test MOI.get(model, MOI.ConstraintPrimal(1), c) ≈ 1.0 atol = atol rtol = + end + @test MOI.get(model, MOI.PrimalStatus(1)) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.PrimalStatus(result_index)) == MOI.NO_SOLUTION + @test MOI.get(model, MOI.VariablePrimal(1), x) ≈ 1.0 atol = atol rtol = rtol + @test_throws result_err(MOI.VariablePrimal(result_index)) MOI.get( + model, + MOI.VariablePrimal(result_index), + x, + ) + @test MOI.get(model, MOI.ConstraintPrimal(1), c) ≈ 1.0 atol = atol rtol = + rtol + @test_throws result_err(MOI.ConstraintPrimal(result_index)) MOI.get( + model, + MOI.ConstraintPrimal(result_index), + c, + ) + if config.duals + @test MOI.get(model, MOI.DualStatus(1)) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.DualStatus(result_index)) == MOI.NO_SOLUTION + @test MOI.get(model, MOI.ConstraintDual(1), c) ≈ 1.0 atol = atol rtol = rtol - @test_throws result_err(MOI.ConstraintPrimal(result_index)) MOI.get( + @test_throws result_err(MOI.ConstraintDual(result_index)) MOI.get( model, - MOI.ConstraintPrimal(result_index), + MOI.ConstraintDual(result_index), c, ) - if config.duals - @test MOI.get(model, MOI.DualStatus(1)) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.DualStatus(result_index)) == - MOI.NO_SOLUTION - @test MOI.get(model, MOI.ConstraintDual(1), c) ≈ 1.0 atol = atol rtol = - rtol - @test_throws result_err(MOI.ConstraintDual(result_index)) MOI.get( - model, - MOI.ConstraintDual(result_index), - c, - ) - end end + return end -unittests["solve_result_index"] = solve_result_index -function solve_farkas_equalto_upper(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_result_index), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], + ), + ) + return +end + +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of an equality constraint violated above. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -242,21 +388,53 @@ function solve_farkas_equalto_upper(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), MOI.EqualTo(-1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] < -config.atol - @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] < -config.atol + @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol + return +end + +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0], + ), + ) + return end -unittests["solve_farkas_equalto_upper"] = solve_farkas_equalto_upper -function solve_farkas_equalto_lower(model::MOI.ModelLike, config::Config) +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of an equality constraint violated below. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_lower( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -266,21 +444,52 @@ function solve_farkas_equalto_lower(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), MOI.EqualTo(1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] > config.atol - @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] > config.atol + @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol end -unittests["solve_farkas_equalto_lower"] = solve_farkas_equalto_lower -function solve_farkas_lessthan(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_lower), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [1.0], + ), + ) + return +end + +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of a less-than constraint. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_LessThan( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -290,21 +499,53 @@ function solve_farkas_lessthan(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), MOI.LessThan(-1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] < -config.atol - @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] < -config.atol + @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol + return end -unittests["solve_farkas_lessthan"] = solve_farkas_lessthan -function solve_farkas_greaterthan(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_LessThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + ) + return +end + +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of a greater-than constraint. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_GreaterThan( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -314,21 +555,53 @@ function solve_farkas_greaterthan(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), MOI.GreaterThan(1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] > config.atol - @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] > config.atol + @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol + return +end + +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_GreaterThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [1.0], + ), + ) + return end -unittests["solve_farkas_greaterthan"] = solve_farkas_greaterthan -function solve_farkas_interval_upper(model::MOI.ModelLike, config::Config) +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of an interval constraint violated above. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_upper( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -338,21 +611,53 @@ function solve_farkas_interval_upper(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), MOI.Interval(-2.0, -1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] < -config.atol - @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] < -config.atol + @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol + return +end + +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_upper), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [-1.0], + ), + ) + return end -unittests["solve_farkas_interval_upper"] = solve_farkas_interval_upper -function solve_farkas_interval_lower(model::MOI.ModelLike, config::Config) +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of an interval constraint violated below. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_lower( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = @@ -362,21 +667,53 @@ function solve_farkas_interval_lower(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], x), 0.0), MOI.Interval(1.0, 2.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] > config.atol - @test clb_dual[2] > config.atol - @test c_dual[1] > config.atol - @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] > config.atol + @test clb_dual[2] > config.atol + @test c_dual[1] > config.atol + @test clb_dual ≈ [2, 1] .* c_dual atol = config.atol rtol = config.rtol + return end -unittests["solve_farkas_interval_lower"] = solve_farkas_interval_lower -function solve_farkas_variable_lessthan(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_Interval_lower), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [1.0], + ), + ) + return +end + +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of a variable upper bound violated above when minimizing. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_SingleVariable_LessThan( + model::MOI.ModelLike, + config::Config, +) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.LessThan(0.0)) @@ -385,24 +722,53 @@ function solve_farkas_variable_lessthan(model::MOI.ModelLike, config::Config) MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0], x), 0.0), MOI.GreaterThan(1.0), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] < -config.atol - @test clb_dual[2] < -config.atol - @test c_dual[1] > config.atol - @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] < -config.atol + @test clb_dual[2] < -config.atol + @test c_dual[1] > config.atol + @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol + return +end + +function setup_test( + ::typeof(test_DualStatus_INFEASIBILITY_CERTIFICATE_SingleVariable_LessThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0, -1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [1.0], + ), + ) + return end -unittests["solve_farkas_variable_lessthan"] = solve_farkas_variable_lessthan -function solve_farkas_variable_lessthan_max( +""" + test_DualStatus_INFEASIBILITY_CERTIFICATE_EqualTo_upper( + model::MOI.ModelLike, + config::Config, + ) + +Test the Farkas dual of a variable upper bound violated above when maximizing. +""" +function test_DualStatus_INFEASIBILITY_CERTIFICATE_SingleVariable_LessThan_max( model::MOI.ModelLike, config::Config, ) + if !(config.solve && config.infeas_certificates) + return + end MOI.empty!(model) x = MOI.add_variables(model, 2) clb = MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.LessThan(0.0)) @@ -417,36 +783,79 @@ function solve_farkas_variable_lessthan_max( MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x[1]), ) - if config.solve && config.infeas_certificates - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE - @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE - clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) - c_dual = MOI.get(model, MOI.ConstraintDual(), c) - @test clb_dual[1] < -config.atol - @test clb_dual[2] < -config.atol - @test c_dual[1] > config.atol - @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol - end + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + clb_dual = MOI.get.(model, MOI.ConstraintDual(), clb) + c_dual = MOI.get(model, MOI.ConstraintDual(), c) + @test clb_dual[1] < -config.atol + @test clb_dual[2] < -config.atol + @test c_dual[1] > config.atol + @test clb_dual ≈ [2, 1] .* -c_dual atol = config.atol rtol = config.rtol + return +end + +function setup_test( + ::typeof( + test_DualStatus_INFEASIBILITY_CERTIFICATE_SingleVariable_LessThan_max, + ), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + (MOI.NO_SOLUTION, [NaN, NaN]), + MOI.INFEASIBILITY_CERTIFICATE, + (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0, -1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [1.0], + ), + ) + return end -unittests["solve_farkas_variable_lessthan_max"] = - solve_farkas_variable_lessthan_max -function solve_twice(model::MOI.ModelLike, config::Config) +""" + test_optimize_twice( + model::MOI.ModelLike, + config::Config, + ) + +Test that calling `optimize!` twice in succession does not error. +""" +function test_optimize_twice(model::MOI.ModelLike, config::Config{T}) where {T} + if !config.solve + return + end MOI.empty!(model) x = MOI.add_variable(model) - c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0)) + MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(one(T))) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set( model, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(x), ) - if config.solve - MOI.optimize!(model) - MOI.optimize!(model) - MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL - MOI.get(model, MOI.VariablePrimal(), x) == 1.0 - end + MOI.optimize!(model) + MOI.optimize!(model) + MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL + MOI.get(model, MOI.VariablePrimal(), x) == one(T) + return +end + +function setup_test( + ::typeof(test_optimize_twice), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return end -unittests["solve_twice"] = solve_twice diff --git a/src/Test/UnitTests/unit_tests.jl b/src/Test/UnitTests/unit_tests.jl deleted file mode 100644 index 0bf89496a5..0000000000 --- a/src/Test/UnitTests/unit_tests.jl +++ /dev/null @@ -1,102 +0,0 @@ -#= - These tests aim to minimally test each expected feature in MOI, in addition - to the full end-to-end tests in contlinear.jl etc -=# - -const unittests = Dict{String,Function}() - -""" - test_model_solution(model::MOI.ModelLike, config::Config; - objective_value = nothing, - variable_primal = nothing, - constraint_primal = nothing, - constraint_dual = nothing - ) - -Solve, and then test, various aspects of a model. - -First, check that `TerminationStatus == MOI.OPTIMAL`. - -If `objective_value` is not nothing, check that the attribute `ObjectiveValue()` -is approximately `objective_value`. - -If `variable_primal` is not nothing, check that the attribute `PrimalStatus` is -`MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `variable_primal`, check -that the primal value of the variable `index` is approximately `value`. - -If `constraint_primal` is not nothing, check that the attribute `PrimalStatus` is -`MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `constraint_primal`, check -that the primal value of the constraint `index` is approximately `value`. - -Finally, if `config.duals = true`, and if `constraint_dual` is not nothing, -check that the attribute `DualStatus` is `MOI.FEASIBLE_POINT`. Then for each -`(index, value)` in `constraint_dual`, check that the dual of the constraint -`index` is approximately `value`. - -### Example - - MOIU.loadfromstring!(model, \"\"\" - variables: x - minobjective: 2.0x + 1.0 - c: x >= 1.0 - \"\"\") - x = MOI.get(model, MOI.VariableIndex, "x") - c = MOI.get(model, MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}}, "c") - test_model_solution(model, config; - objective_value = 3.0, - variable_primal = [(x, 1.0)], - constraint_primal = [(c, 1.0)], - constraint_dual = [(c, 2.0)] - ) - -""" -function test_model_solution( - model, - config; - objective_value = nothing, - variable_primal = nothing, - constraint_primal = nothing, - constraint_dual = nothing, -) - config.solve || return - atol, rtol = config.atol, config.rtol - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status - if objective_value != nothing - @test MOI.get(model, MOI.ObjectiveValue()) ≈ objective_value atol = atol rtol = - rtol - end - if variable_primal != nothing - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - for (index, solution_value) in variable_primal - @test MOI.get(model, MOI.VariablePrimal(), index) ≈ solution_value atol = - atol rtol = rtol - end - end - if constraint_primal != nothing - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - for (index, solution_value) in constraint_primal - @test MOI.get(model, MOI.ConstraintPrimal(), index) ≈ solution_value atol = - atol rtol = rtol - end - end - if config.duals - if constraint_dual != nothing - @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT - for (index, solution_value) in constraint_dual - @test MOI.get(model, MOI.ConstraintDual(), index) ≈ - solution_value atol = atol rtol = rtol - end - end - end -end - -include("variables.jl") -include("objectives.jl") -include("constraints.jl") -include("basic_constraint_tests.jl") -include("modifications.jl") -include("solve.jl") -include("attributes.jl") - -@moitestset unit diff --git a/src/Test/UnitTests/variables.jl b/src/Test/UnitTests/variables.jl index 54c042504d..92ea15390b 100644 --- a/src/Test/UnitTests/variables.jl +++ b/src/Test/UnitTests/variables.jl @@ -1,55 +1,37 @@ -#= - Functions in this file test functionality relating to variables in MOI. - -### Functionality currently tested - - add_variables - - add_variable - - deleting variables - - get/set VariableName - - is_valid for VariableIndex - - get VariableIndex by name - - NumberOfVariables - -### Functionality not yet tested - - VariablePrimalStart - - VariablePrimal - - ListOfVariableIndices -=# - """ - add_variable(model::MOI.ModelLike, config::Config) + test_add_variable(model::MOI.ModelLike, config::Config) Test adding a single variable. """ -function add_variable(model::MOI.ModelLike, config::Config) +function test_add_variable(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 v = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 1 + return end -unittests["add_variable"] = add_variable """ - add_variables(model::MOI.ModelLike, config::Config) + test_add_variables(model::MOI.ModelLike, config::Config) Test adding multiple variables. """ -function add_variables(model::MOI.ModelLike, config::Config) +function test_add_variables(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 v = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 2 + return end -unittests["add_variables"] = add_variables """ - delete_variable(model::MOI.ModelLike, config::Config) + test_delete_variable(model::MOI.ModelLike, config::Config) Tess adding, and then deleting, a single variable. """ -function delete_variable(model::MOI.ModelLike, config::Config) +function test_delete_variable(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 @@ -57,15 +39,15 @@ function delete_variable(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.NumberOfVariables()) == 1 MOI.delete(model, v) @test MOI.get(model, MOI.NumberOfVariables()) == 0 + return end -unittests["delete_variable"] = delete_variable """ - delete_variables(model::MOI.ModelLike, config::Config) + test_delete_variables(model::MOI.ModelLike, config::Config) Test adding, and then deleting, multiple variables. """ -function delete_variables(model::MOI.ModelLike, config::Config) +function test_delete_variables(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 @@ -85,15 +67,15 @@ function delete_variables(model::MOI.ModelLike, config::Config) end @test !MOI.is_valid(model, v[1]) @test MOI.is_valid(model, v[2]) + return end -unittests["delete_variables"] = delete_variable """ - delete_nonnegative_variables(model::MOI.ModelLike, config::Config) + test_delete_nonnegative_variables(model::MOI.ModelLike, config::Config) Test adding, and then deleting, nonnegative variables. """ -function delete_nonnegative_variables(model::MOI.ModelLike, config::Config) +function test_delete_nonnegative_variables(model::MOI.ModelLike, ::Config) MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 @@ -113,17 +95,17 @@ function delete_nonnegative_variables(model::MOI.ModelLike, config::Config) @test_throws MOI.InvalidIndex(v[1]) MOI.delete(model, v[1]) @test !MOI.is_valid(model, cv) @test MOI.get(model, MOI.NumberOfVariables()) == 0 + return end -unittests["delete_nonnegative_variables"] = delete_nonnegative_variables """ - update_dimension_nonnegative_variables(model::MOI.ModelLike, config::Config) + test_update_dimension_nonnegative_variables(model::MOI.ModelLike, ::Config) Test adding, and then deleting one by one, nonnegative variables. """ -function update_dimension_nonnegative_variables( +function test_update_dimension_nonnegative_variables( model::MOI.ModelLike, - config::Config, + ::Config, ) MOI.empty!(model) @test MOI.is_empty(model) @@ -145,17 +127,18 @@ function update_dimension_nonnegative_variables( @test !MOI.is_valid(model, v[2]) @test_throws MOI.InvalidIndex(v[2]) MOI.delete(model, v[2]) @test !MOI.is_valid(model, cv) + return end -unittests["update_dimension_nonnegative_variables"] = - update_dimension_nonnegative_variables """ - delete_soc_variables(model::MOI.ModelLike, config::Config) + test_delete_soc_variables(model::MOI.ModelLike, config::Config) Test adding, and then deleting, second-order cone variables. """ -function delete_soc_variables(model::MOI.ModelLike, config::Config) - MOI.supports_add_constrained_variables(model, MOI.SecondOrderCone) || return +function test_delete_soc_variables(model::MOI.ModelLike, config::Config) + if !MOI.supports_add_constrained_variables(model, MOI.SecondOrderCone) + return + end MOI.empty!(model) @test MOI.is_empty(model) @test MOI.get(model, MOI.NumberOfVariables()) == 0 @@ -171,15 +154,15 @@ function delete_soc_variables(model::MOI.ModelLike, config::Config) v, cv = MOI.add_constrained_variables(model, MOI.SecondOrderCone(3)) @test MOI.get(model, MOI.NumberOfVariables()) == 3 @test_throws MOI.DeleteNotAllowed MOI.delete(model, v[1]) + return end -unittests["delete_soc_variables"] = delete_soc_variables """ - getvariable(model::MOI.ModelLike, config::Config) + test_get_variable(model::MOI.ModelLike, config::Config) Test getting variables by name. """ -function getvariable(model::MOI.ModelLike, config::Config) +function test_get_variable_by_name(model::MOI.ModelLike, ::Config) MOI.empty!(model) MOIU.loadfromstring!( model, @@ -193,15 +176,15 @@ function getvariable(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariableIndex, "y") === nothing x = MOI.get(model, MOI.VariableIndex, "x") @test MOI.is_valid(model, x) + return end -unittests["getvariable"] = getvariable """ - variablenames(model::MOI.ModelLike, config::Config) + test_VariableName(model::MOI.ModelLike, config::Config) Test getting and setting variable names. """ -function variablenames(model::MOI.ModelLike, config::Config) +function test_VariableName(model::MOI.ModelLike, ::Config) MOI.empty!(model) v = MOI.add_variable(model) @test MOI.get(model, MOI.VariableName(), v) == "" @@ -213,17 +196,16 @@ function variablenames(model::MOI.ModelLike, config::Config) x = MOI.add_variable(model) MOI.set(model, MOI.VariableName(), x, "x") @test MOI.get(model, MOI.VariableName(), x) == "x" + return end -unittests["variablenames"] = variablenames """ - solve_with_upperbound(model::MOI.ModelLike, config::Config) + test_solve_with_upperbound(model::MOI.ModelLike, config::Config) Test setting the upper bound of a variable, confirm that it solves correctly, and if `config.duals=true`, check that the dual is computed correctly. """ -function solve_with_upperbound(model::MOI.ModelLike, config::Config) - atol, rtol = config.atol, config.rtol +function test_solve_with_upperbound(model::MOI.ModelLike, config::Config) MOI.empty!(model) @test MOI.is_empty(model) MOIU.loadfromstring!( @@ -244,7 +226,7 @@ function solve_with_upperbound(model::MOI.ModelLike, config::Config) x.value, ) @test c2.value == x.value - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, @@ -252,17 +234,36 @@ function solve_with_upperbound(model::MOI.ModelLike, config::Config) constraint_primal = [(c1, 1.0), (c2, 1.0)], constraint_dual = [(c1, -2.0), (c2, 0.0)], ) + return +end + +function setup_test( + ::typeof(test_solve_with_upperbound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0], + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0], + ), + ) + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = true end -unittests["solve_with_upperbound"] = solve_with_upperbound """ - solve_with_lowerbound(model::MOI.ModelLike, config::Config) + test_solve_with_lowerbound(model::MOI.ModelLike, config::Config) Test setting the lower bound of a variable, confirm that it solves correctly, and if `config.duals=true`, check that the dual is computed correctly. """ -function solve_with_lowerbound(model::MOI.ModelLike, config::Config) - atol, rtol = config.atol, config.rtol +function test_solve_with_lowerbound(model::MOI.ModelLike, config::Config) MOI.empty!(model) @test MOI.is_empty(model) MOIU.loadfromstring!( @@ -281,7 +282,7 @@ function solve_with_lowerbound(model::MOI.ModelLike, config::Config) @test c1.value == x.value c2 = MOI.ConstraintIndex{MOI.SingleVariable,MOI.LessThan{Float64}}(x.value) @test c2.value == x.value - return test_model_solution( + _test_model_solution( model, config; objective_value = 2.0, @@ -289,94 +290,209 @@ function solve_with_lowerbound(model::MOI.ModelLike, config::Config) constraint_primal = [(c1, 1.0), (c2, 1.0)], constraint_dual = [(c1, 2.0), (c2, 0.0)], ) + return +end + +function setup_test( + ::typeof(test_solve_with_lowerbound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1]), + MOI.FEASIBLE_POINT, + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0], + (MOI.SingleVariable, MOI.LessThan{Float64}) => [0.0], + ), + ) + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = true end -unittests["solve_with_lowerbound"] = solve_with_lowerbound """ - solve_integer_edge_cases(model::MOI.ModelLike, config::Config) + test_solve_Integer_with_lower_bound( + model::MOI.ModelLike, + config::Config, + ) -Test a variety of edge cases related to binary and integer variables. +Test an integer variable with fractional lower bound. """ -function solve_integer_edge_cases(model::MOI.ModelLike, config::Config) - @testset "integer with lower bound" begin - MOI.empty!(model) - @test MOI.is_empty(model) - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: 2.0x - x >= 1.5 - x in Integer() +function test_solve_Integer_with_lower_bound( + model::MOI.ModelLike, + config::Config, +) + MOI.empty!(model) + @test MOI.is_empty(model) + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: 2.0x +x >= 1.5 +x in Integer() """, - ) - x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( - model, - config; - objective_value = 4.0, - variable_primal = [(x, 2.0)], - ) - end - @testset "integer with upper bound" begin - MOI.empty!(model) - @test MOI.is_empty(model) - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: -2.0x - x <= 1.5 - x in Integer() + ) + x = MOI.get(model, MOI.VariableIndex, "x") + _test_model_solution( + model, + config; + objective_value = 4.0, + variable_primal = [(x, 2.0)], + ) + return +end + +function setup_test( + ::typeof(test_solve_Integer_with_lower_bound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [2.0])), + ) + return +end + +""" + test_solve_Integer_with_upper_bound( + model::MOI.ModelLike, + config::Config, + ) + +Test an integer variable with fractional upper bound. +""" +function test_solve_Integer_with_upper_bound( + model::MOI.ModelLike, + config::Config, +) + MOI.empty!(model) + @test MOI.is_empty(model) + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: -2.0x +x <= 1.5 +x in Integer() """, - ) - x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( - model, - config; - objective_value = -2.0, - variable_primal = [(x, 1.0)], - ) - end - @testset "binary with upper" begin - MOI.empty!(model) - @test MOI.is_empty(model) - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: -2.0x - x <= 2.0 - x in ZeroOne() + ) + x = MOI.get(model, MOI.VariableIndex, "x") + _test_model_solution( + model, + config; + objective_value = -2.0, + variable_primal = [(x, 1.0)], + ) + return +end + +function setup_test( + ::typeof(test_solve_Integer_with_upper_bound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return +end + +""" + test_solve_Integer_with_0_upper_bound( + model::MOI.ModelLike, + config::Config, + ) + +Test a binary variable `<= 2`. +""" +function test_solve_ZeroOne_with_upper_bound( + model::MOI.ModelLike, + config::Config, +) + MOI.empty!(model) + @test MOI.is_empty(model) + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: -2.0x +x <= 2.0 +x in ZeroOne() """, - ) - x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( - model, - config; - objective_value = -2.0, - variable_primal = [(x, 1.0)], - ) - end - @testset "binary with 0 upper" begin - MOI.empty!(model) - @test MOI.is_empty(model) - MOIU.loadfromstring!( - model, - """ - variables: x - minobjective: 1.0x - x <= 0.0 - x in ZeroOne() + ) + x = MOI.get(model, MOI.VariableIndex, "x") + _test_model_solution( + model, + config; + objective_value = -2.0, + variable_primal = [(x, 1.0)], + ) + return +end + +function setup_test( + ::typeof(test_solve_ZeroOne_with_upper_bound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])), + ) + return +end + +""" + test_solve_ZeroOne_with_0_upper_bound( + model::MOI.ModelLike, + config::Config, + ) + +Test a binary variable `<= 0`. +""" +function test_solve_ZeroOne_with_0_upper_bound( + model::MOI.ModelLike, + config::Config, +) + MOI.empty!(model) + @test MOI.is_empty(model) + MOIU.loadfromstring!( + model, + """ +variables: x +minobjective: 1.0x +x <= 0.0 +x in ZeroOne() """, - ) - x = MOI.get(model, MOI.VariableIndex, "x") - test_model_solution( - model, - config; - objective_value = 0.0, - variable_primal = [(x, 0.0)], - ) - end + ) + x = MOI.get(model, MOI.VariableIndex, "x") + _test_model_solution( + model, + config; + objective_value = 0.0, + variable_primal = [(x, 0.0)], + ) + return +end + +function setup_test( + ::typeof(test_solve_ZeroOne_with_0_upper_bound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])), + ) + return end -unittests["solve_integer_edge_cases"] = solve_integer_edge_cases diff --git a/src/Test/config.jl b/src/Test/config.jl deleted file mode 100644 index 01a8d8e358..0000000000 --- a/src/Test/config.jl +++ /dev/null @@ -1,124 +0,0 @@ -struct Config{T<:Real} - atol::T # absolute tolerance for ... - rtol::T # relative tolerance for ... - solve::Bool # optimize and test result - query_number_of_constraints::Bool # can get `MOI.NumberOfConstraints` attribute - query::Bool # can get objective function, and constraint functions, and constraint sets - modify_lhs::Bool # can modify function of a constraint - duals::Bool # test dual solutions - dual_objective_value::Bool # test `DualObjectiveValue` - infeas_certificates::Bool # check for primal or dual infeasibility certificates when appropriate - # The expected "optimal" status returned by the solver. Either - # MOI.OPTIMAL or MOI.LOCALLY_SOLVED. - optimal_status::MOI.TerminationStatusCode - basis::Bool # can get variable and constraint basis status - - """ - Config{T}(; - atol::Real = Base.rtoldefault(T), - rtol::Real = Base.rtoldefault(T), - solve::Bool = true, - query_number_of_constraints::Bool = true, - query::Bool = true, - modify_lhs::Bool = true, - duals::Bool = true, - dual_objective_value::Bool = duals, - infeas_certificates::Bool = true, - optimal_status = MOI.OPTIMAL, - basis::Bool = false, - ) - - Return an object that is used to configure various tests. - - ## Keywords - - * `atol::Real = Base.rtoldefault(T)`: Control the absolute tolerance used - when comparing solutions. - * `rtol::Real = Base.rtoldefault(T)`: Control the relative tolerance used - when comparing solutions. - * `solve::Bool = true`: Set to `false` to skip tests requiring a call to - [`MOI.optimize!`](@ref) - * `query_number_of_constraints::Bool = true`: Set to `false` to skip tests - requiring a call to [`MOI.NumberOfConstraints`](@ref). - * `query::Bool = true`: Set to `false` to skip tests requiring a call to - [`MOI.get`](@ref) for [`MOI.ConstraintFunction`](@ref) and - [`MOI.ConstraintSet`](@ref) - * `modify_lhs::Bool = true`: - * `duals::Bool = true`: Set to `false` to skip tests querying - [`MOI.ConstraintDual`](@ref). - * `dual_objective_value::Bool = duals`: Set to `false` to skip tests - querying [`MOI.DualObjectiveValue`](@ref). - * `infeas_certificates::Bool = true`: Set to `false` to skip tests querying - primal and dual infeasibility certificates. - * `optimal_status = MOI.OPTIMAL`: Set to `MOI.LOCALLY_SOLVED` if the solver - cannot prove global optimality. - * `basis::Bool = false`: Set to `true` if the solver supports - [`MOI.ConstraintBasisStatus`](@ref) and [`MOI.VariableBasisStatus`](@ref). - """ - function Config{T}(; - atol::Real = Base.rtoldefault(T), - rtol::Real = Base.rtoldefault(T), - solve::Bool = true, - query_number_of_constraints::Bool = true, - query::Bool = true, - modify_lhs::Bool = true, - duals::Bool = true, - dual_objective_value::Bool = duals, - infeas_certificates::Bool = true, - optimal_status = MOI.OPTIMAL, - basis::Bool = false, - ) where {T<:Real} - return new( - atol, - rtol, - solve, - query_number_of_constraints, - query, - modify_lhs, - duals, - dual_objective_value, - infeas_certificates, - optimal_status, - basis, - ) - end - Config(; kwargs...) = Config{Float64}(; kwargs...) -end - -@deprecate TestConfig Config - -""" - @moitestset setname subsets - -Defines a function `setnametest(model, config, exclude)` that runs the tests defined in the dictionary `setnametests` -with the model `model` and config `config` except the tests whose dictionary key is in `exclude`. -If `subsets` is `true` then each test runs in fact multiple tests hence the `exclude` argument is passed -as it can also contains test to be excluded from these subsets of tests. -""" -macro moitestset(setname, subsets = false) - testname = Symbol(string(setname) * "test") - testdict = Symbol(string(testname) * "s") - if subsets - runtest = :(f(model, config, exclude)) - else - runtest = :(f(model, config)) - end - return esc( - :( - function $testname( - model::$MOI.ModelLike, - config::$MOI.Test.Config, - exclude::Vector{String} = String[], - ) - for (name, f) in $testdict - if name in exclude - continue - end - @testset "$name" begin - $runtest - end - end - end - ), - ) -end diff --git a/src/Test/contconic.jl b/src/Test/contconic.jl index 0f395532a4..c44fb674e3 100644 --- a/src/Test/contconic.jl +++ b/src/Test/contconic.jl @@ -1,22 +1,35 @@ -# Continuous conic problems -using LinearAlgebra # for dot +""" + _test_conic_linear_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, + ) + +A helper function for writing other conic tests. -function _lin1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +Constructs the problem: +``` +min -3x - 2y - 4z +st x + y + z == 3 + y + z == 2 + x>=0 y>=0 z>=0 +Opt obj = -11, soln x = 1, y = 0, z = 2 +``` +""" +function _test_conic_linear_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) atol = config.atol rtol = config.rtol - # linear conic problem - # min -3x - 2y - 4z - # st x + y + z == 3 - # y + z == 2 - # x>=0 y>=0 z>=0 - # Opt obj = -11, soln x = 1, y = 0, z = 2 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -39,7 +52,7 @@ function _lin1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) v = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == 3 vov = MOI.VectorOfVariables(v) - if vecofvars + if use_VectorOfVariables vc = MOI.add_constraint(model, vov, MOI.Nonnegatives(3)) else vc = MOI.add_constraint( @@ -63,7 +76,7 @@ function _lin1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, }(), @@ -79,7 +92,8 @@ function _lin1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) loc = MOI.get(model, MOI.ListOfConstraintTypesPresent()) @test length(loc) == 2 @test ( - vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, + use_VectorOfVariables ? MOI.VectorOfVariables : + MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, ) in loc @test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc @@ -118,42 +132,106 @@ function _lin1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) rtol end end + return +end + +""" + test_conic_linear_VectorOfVariables(model::MOI.ModelLike, config::Config) + +Test a conic formulation of a linear program using standard conic form. +""" +function test_conic_linear_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_linear_helper(model, config, true) + return end -function lin1vtest(model::MOI.ModelLike, config::Config) - return _lin1test(model, config, true) +function setup_test( + ::typeof(test_conic_linear_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 0.0, 2.0], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]], + ), + ) + return end -function lin1ftest(model::MOI.ModelLike, config::Config) - return _lin1test(model, config, false) + +""" + test_conic_linear_VectorAffineFunction(model::MOI.ModelLike, config::Config) + +Test a conic formulation of a linear program using geometric conic form. +""" +function test_conic_linear_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_linear_helper(model, config, false) + return end -function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +function setup_test( + ::typeof(test_conic_linear_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 0.0, 2.0], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[0, 2, 0]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]], + ), + ) + return +end + +""" + _test_conic_linear_helper_2( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, + ) + +Another helper for linear conic problems. + +Builds the problem: +``` +min 3x + 2y - 4z + 0s +st x - s == -4 (i.e. x >= -4) + y == -3 + x + z == 12 + x free + y <= 0 + z >= 0 + s zero +Opt solution = -82 +x = -4, y = -3, z = 16, s == 0 +``` +""" +function _test_conic_linear_helper_2( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) atol = config.atol rtol = config.rtol - #@test MOI.supportsproblem(model, MOI.ScalarAffineFunction{Float64}, - #[ - # (MOI.VectorAffineFunction{Float64},MOI.Zeros), - # (MOI.VectorOfVariables,MOI.Nonnegatives), - # (MOI.VectorOfVariables,MOI.Nonpositives) - #]) - # mixed cones - # min 3x + 2y - 4z + 0s - # st x - s == -4 (i.e. x >= -4) - # y == -3 - # x + z == 12 - # x free - # y <= 0 - # z >= 0 - # s zero - # Opt solution = -82 - # x = -4, y = -3, z = 16, s == 0 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -182,7 +260,7 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.is_empty(model) x = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 1 - if vecofvars + if use_VectorOfVariables ys, vc = MOI.add_constrained_variables(model, MOI.Nonpositives(1)) y = ys[1] else @@ -216,7 +294,7 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) ), MOI.Zeros(3), ) - if vecofvars + if use_VectorOfVariables # test fallback vz = MOI.add_constraint(model, [z], MOI.Nonnegatives(1)) else @@ -230,7 +308,7 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) ) end vov = MOI.VectorOfVariables([s]) - if vecofvars + if use_VectorOfVariables vs = MOI.add_constraint(model, vov, MOI.Zeros(1)) else vs = MOI.add_constraint( @@ -246,11 +324,11 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.VectorAffineFunction{Float64}, MOI.Zeros, }(), - ) == 2 - vecofvars + ) == 2 - use_VectorOfVariables @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.Nonpositives, }(), @@ -258,7 +336,7 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, }(), @@ -304,26 +382,89 @@ function _lin2test(model::MOI.ModelLike, config::Config, vecofvars::Bool) rtol end end + return end -function lin2vtest(model::MOI.ModelLike, config::Config) - return _lin2test(model, config, true) +""" + test_conic_linear_VectorOfVariables_2( + model::MOI.ModelLike, + config::Config, + ) + +Test a linear program in standard conic form. +""" +function test_conic_linear_VectorOfVariables_2( + model::MOI.ModelLike, + config::Config, +) + _test_conic_linear_helper_2(model, config, true) + return end -function lin2ftest(model::MOI.ModelLike, config::Config) - return _lin2test(model, config, false) + +function setup_test( + ::typeof(test_conic_linear_VectorOfVariables_2), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [-4, -3, 16, 0], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[7, 2, -4]], + ), + ) + return end -function lin3test(model::MOI.ModelLike, config::Config) - atol = config.atol - rtol = config.rtol - # Problem LIN3 - Infeasible LP - # min 0 - # s.t. x ≥ 1 - # x ≤ -1 - # in conic form: - # min 0 - # s.t. -1 + x ∈ R₊ - # 1 + x ∈ R₋ +""" + test_conic_linear_VectorAffineFunction_2( + model::MOI.ModelLike, + config::Config, + ) + +Test a linear program in geometric conic form. +""" +function test_conic_linear_VectorAffineFunction_2( + model::MOI.ModelLike, + config::Config, +) + _test_conic_linear_helper_2(model, config, false) + return +end + +function setup_test( + ::typeof(test_conic_linear_VectorAffineFunction_2), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [-4, -3, 16, 0], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0]], + (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[0]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => + [[7, 2, -4], [7]], + ), + ) + return +end + +""" + test_conic_linear_INFEASIBLE(model::MOI.ModelLike, config::Config) + +Test an infeasible linear program in conic form. + +The problem is: +``` +min 0 +s.t. -1 + x ∈ R₊ + 1 + x ∈ R₋ +``` +""" +function test_conic_linear_INFEASIBLE(model::MOI.ModelLike, config::Config) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -395,19 +536,39 @@ function lin3test(model::MOI.ModelLike, config::Config) end # TODO test dual feasibility and objective sign end + return end -function lin4test(model::MOI.ModelLike, config::Config) - atol = config.atol - rtol = config.rtol - # Problem LIN4 - Infeasible LP - # min 0 - # s.t. x ≥ 1 - # x ≤ 0 - # in conic form: - # min 0 - # s.t. -1 + x ∈ R₊ - # x ∈ R₋ +function setup_test( + ::typeof(test_conic_linear_INFEASIBLE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.INFEASIBLE_POINT, + MOI.INFEASIBILITY_CERTIFICATE, + ), + ) + return +end + +""" + test_conic_linear_INFEASIBLE_2(model::MOI.ModelLike, config::Config) + +Test an infeasible linear program in conic form. + +The problem is: +``` +min 0 +s.t. -1 + x ∈ R₊ + x ∈ R₋ +``` +""" +function test_conic_linear_INFEASIBLE_2(model::MOI.ModelLike, config::Config) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -465,20 +626,48 @@ function lin4test(model::MOI.ModelLike, config::Config) end # TODO test dual feasibility and objective sign end + return end -const lintests = Dict( - "lin1v" => lin1vtest, - "lin1f" => lin1ftest, - "lin2v" => lin2vtest, - "lin2f" => lin2ftest, - "lin3" => lin3test, - "lin4" => lin4test, +function setup_test( + ::typeof(test_conic_linear_INFEASIBLE_2), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.INFEASIBLE_POINT, + MOI.INFEASIBILITY_CERTIFICATE, + ), + ) + return +end -@moitestset lin +""" + _test_conic_NormInfinityCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, + ) -function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +A helper function for testing NormInfinityCone. +""" +function _test_conic_NormInfinityCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) + F = if use_VectorOfVariables + MOI.VectorOfVariables + else + MOI.VectorAffineFunction{Float64} + end + if !MOI.supports_constraint(model, F, MOI.NormInfinityCone) + return + end atol = config.atol rtol = config.rtol # Problem NormInf1 @@ -492,7 +681,7 @@ function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) else @test MOI.supports_constraint( @@ -535,7 +724,7 @@ function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.Zeros(1), ) vov = MOI.VectorOfVariables([x, y, z]) - if vecofvars + if use_VectorOfVariables ccone = MOI.add_constraint(model, vov, MOI.NormInfinityCone(3)) else ccone = MOI.add_constraint( @@ -555,7 +744,7 @@ function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone, }(), @@ -565,7 +754,8 @@ function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test length(loc) == 2 @test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc @test ( - vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, + use_VectorOfVariables ? MOI.VectorOfVariables : + MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone, ) in loc if config.solve @@ -602,28 +792,94 @@ function _norminf1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) atol rtol = rtol end end + return +end + +""" + test_conic_NormInfinityCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test a NormInfinityCone in standard conic form. +""" +function test_conic_NormInfinityCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_NormInfinityCone_helper(model, config, true) + return end -function norminf1vtest(model::MOI.ModelLike, config::Config) - return _norminf1test(model, config, true) +function setup_test( + ::typeof(test_conic_NormInfinityCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0.5, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [-1]], + (MOI.VectorOfVariables, MOI.NormInfinityCone) => [[1, 0, -1]], + ), + ) + return end -function norminf1ftest(model::MOI.ModelLike, config::Config) - return _norminf1test(model, config, false) + +""" + test_conic_NormInfinityCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test a NormInfinityCone in geometric conic form. +""" +function test_conic_NormInfinityCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_NormInfinityCone_helper(model, config, false) + return end -function norminf2test(model::MOI.ModelLike, config::Config) - atol = config.atol - rtol = config.rtol - # Problem NormInf2 - Infeasible - # min 0 - # s.t. y ≥ 2 - # x ≤ 1 - # |y| ≤ x - # in conic form: - # min 0 - # s.t. -2 + y ∈ R₊ - # -1 + x ∈ R₋ - # (x,y) ∈ NormInf₂ +function setup_test( + ::typeof(test_conic_NormInfinityCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0.5, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [-1]], + (MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) => + [[1, 0, -1]], + ), + ) + return +end + +""" + test_conic_NormInfinityCone_INFEASIBLE( + model::MOI.ModelLike, + config::Config, + ) + +Test the problem: +``` +min 0 +s.t. -2 + y ∈ R₊ + -1 + x ∈ R₋ + (x,y) ∈ NormInf₂ +``` +""" +function test_conic_NormInfinityCone_INFEASIBLE( + model::MOI.ModelLike, + config::Config, +) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -707,16 +963,40 @@ function norminf2test(model::MOI.ModelLike, config::Config) end # TODO test dual feasibility and objective sign end + return end -function norminf3test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_conic_NormInfinityCone_INFEASIBLE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.INFEASIBLE_POINT, + MOI.INFEASIBILITY_CERTIFICATE, + ), + ) + return +end + +""" + test_conic_NormInfinityCone_3(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +min x + st (-1 + x, 2 .+ y) in NormInf(1 + n) + (1 .+ y) in Nonnegatives(n) +let n = 3. optimal solution: y .= -1, x = 2 +``` +""" +function test_conic_NormInfinityCone_3(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # Problem NormInf3 - # min x - # st (-1 + x, 2 .+ y) in NormInf(1 + n) - # (1 .+ y) in Nonnegatives(n) - # let n = 3. optimal solution: y .= -1, x = 2 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -794,18 +1074,33 @@ function norminf3test(model::MOI.ModelLike, config::Config) vcat(1, -dual_nonneg) atol = atol rtol = rtol end end + return end -const norminftests = Dict( - "norminf1v" => norminf1vtest, - "norminf1f" => norminf1ftest, - "norminf2" => norminf2test, - "norminf3" => norminf3test, +function setup_test( + ::typeof(test_conic_NormInfinityCone_3), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [2, -1, -1, -1], + (MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) => + [vcat(1, fill(-inv(3), 3))], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [fill(inv(3), 3)], + ), + ) + return +end -@moitestset norminf - -function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +function _test_conic_NormOneCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) atol = config.atol rtol = config.rtol # Problem NormOne1 @@ -819,7 +1114,7 @@ function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) else @test MOI.supports_constraint( @@ -858,7 +1153,7 @@ function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.Zeros(1), ) vov = MOI.VectorOfVariables([x, y, z]) - if vecofvars + if use_VectorOfVariables ccone = MOI.add_constraint(model, vov, MOI.NormOneCone(3)) else ccone = MOI.add_constraint( @@ -878,7 +1173,7 @@ function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.NormOneCone, }(), @@ -888,7 +1183,8 @@ function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test length(loc) == 2 @test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc @test ( - vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, + use_VectorOfVariables ? MOI.VectorOfVariables : + MOI.VectorAffineFunction{Float64}, MOI.NormOneCone, ) in loc if config.solve @@ -925,28 +1221,91 @@ function _normone1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) [1.0, -1.0, -1.0] atol = atol rtol = rtol end end + return end -function normone1vtest(model::MOI.ModelLike, config::Config) - return _normone1test(model, config, true) +""" + test_conic_NormOneCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test NormOneCone in standard conic form. +""" +function test_conic_NormOneCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + return _test_conic_NormOneCone_helper(model, config, true) end -function normone1ftest(model::MOI.ModelLike, config::Config) - return _normone1test(model, config, false) + +function setup_test( + ::typeof(test_conic_NormOneCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0.5, 0.5], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [0]], + (MOI.VectorOfVariables, MOI.NormOneCone) => [[1, -1, -1]], + ), + ) + return end -function normone2test(model::MOI.ModelLike, config::Config) - atol = config.atol - rtol = config.rtol - # Problem NormOne2 - Infeasible - # min 0 - # s.t. y ≥ 2 - # x ≤ 1 - # |y| ≤ x - # in conic form: - # min 0 - # s.t. -2 + y ∈ R₊ - # -1 + x ∈ R₋ - # (x,y) ∈ NormOne₂ +""" + test_conic_NormOneCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test NormOneCone in geometric conic form. +""" +function test_conic_NormOneCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + return _test_conic_NormOneCone_helper(model, config, false) +end + +function setup_test( + ::typeof(test_conic_NormOneCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0.5, 0.5], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [0]], + (MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) => + [[1, -1, -1]], + ), + ) + return +end + +""" + test_conic_NormOneCone_INFEASIBLE(model::MOI.ModelLike, config::Config) + +Test and infeasible problem with NormOneCone. + +Problem NormOne2 - Infeasible +min 0 +s.t. y ≥ 2 + x ≤ 1 + |y| ≤ x +in conic form: +min 0 +s.t. -2 + y ∈ R₊ + -1 + x ∈ R₋ + (x,y) ∈ NormOne₂ +""" +function test_conic_NormOneCone_INFEASIBLE(model::MOI.ModelLike, config::Config) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -1030,16 +1389,47 @@ function normone2test(model::MOI.ModelLike, config::Config) end # TODO test dual feasibility and objective sign end + return end -function normone3test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_conic_NormOneCone_INFEASIBLE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.INFEASIBLE_POINT, + MOI.INFEASIBILITY_CERTIFICATE, + ), + ) + return +end + +""" + test_conic_NormOneCone(model::MOI.ModelLike, config::Config) + +Test the following problem: +``` +min x + st (-1 + x, 2 .+ y) in NormOne(1 + n) + (1 .+ y) in Nonnegatives(n) +let n = 3. optimal solution: y .= -1, x = 4 +``` +""" +function test_conic_NormOneCone(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.NormOneCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem NormOne3 - # min x - # st (-1 + x, 2 .+ y) in NormOne(1 + n) - # (1 .+ y) in Nonnegatives(n) - # let n = 3. optimal solution: y .= -1, x = 4 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -1116,31 +1506,55 @@ function normone3test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return end -const normonetests = Dict( - "normone1v" => normone1vtest, - "normone1f" => normone1ftest, - "normone2" => normone2test, - "normone3" => normone3test, +function setup_test( + ::typeof(test_conic_NormOneCone), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [4, -1, -1, -1], + (MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) => + [vcat(1, fill(-1, 3))], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [ones(3)], + ), + ) + return +end -@moitestset normone - -function _soc1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +""" +Problem SOC1 +max 0x + 1y + 1z + st x == 1 + x >= ||(y,z)|| +""" +function _test_conic_SecondOrderCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem SOC1 - # max 0x + 1y + 1z - # st x == 1 - # x >= ||(y,z)|| @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_add_constrained_variables(model, MOI.SecondOrderCone) else @test MOI.supports_constraint( @@ -1152,7 +1566,7 @@ function _soc1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) MOI.empty!(model) @test MOI.is_empty(model) - if vecofvars + if use_VectorOfVariables xyz, csoc = MOI.add_constrained_variables(model, MOI.SecondOrderCone(3)) x, y, z = xyz else @@ -1192,7 +1606,7 @@ function _soc1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone, }(), @@ -1202,7 +1616,8 @@ function _soc1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) @test length(loc) == 2 @test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc @test ( - vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, + use_VectorOfVariables ? MOI.VectorOfVariables : + MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone, ) in loc if config.solve @@ -1237,14 +1652,84 @@ function _soc1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) end end -function soc1vtest(model::MOI.ModelLike, config::Config) - return _soc1test(model, config, true) +""" + test_conic_SecondOrderCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test a SecondOrderCone in standard conic form. +""" +function test_conic_SecondOrderCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_SecondOrderCone_helper(model, config, true) + return end -function soc1ftest(model::MOI.ModelLike, config::Config) - return _soc1test(model, config, false) + +function setup_test( + ::typeof(test_conic_SecondOrderCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 1 / √2, 1 / √2], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], + ), + ) + return end -function _soc2test(model::MOI.ModelLike, config::Config, nonneg::Bool) +""" + test_conic_SecondOrderCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test a SecondOrderCone in geometric conic form. +""" +function test_conic_SecondOrderCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_SecondOrderCone_helper(model, config, false) + return +end + +function setup_test( + ::typeof(test_conic_SecondOrderCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 1 / √2, 1 / √2], + (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => + [[√2, -1, -1]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], + ), + ) + return +end + +function _test_conic_SecondOrderCone_helper_2( + model::MOI.ModelLike, + config::Config, + nonneg::Bool, +) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone, + ) + return + end atol = config.atol rtol = config.rtol # Problem SOC2 @@ -1390,28 +1875,108 @@ function _soc2test(model::MOI.ModelLike, config::Config, nonneg::Bool) atol rtol = rtol end end + return +end + +""" + test_conic_SecondOrderCone_Nonnegatives( + model::MOI.ModelLike, + config::Config, + ) + +Test a SecondOrderCone with Nonnegatives constraints. +""" +function test_conic_SecondOrderCone_Nonnegatives( + model::MOI.ModelLike, + config::Config, +) + _test_conic_SecondOrderCone_helper_2(model, config, true) + return +end + +function setup_test( + ::typeof(test_conic_SecondOrderCone_Nonnegatives), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [-1 / √2, 1 / √2, 1.0], + (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => + [[√2, 1, -1]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[√2]], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[1.0]], + ), + ) + return end -function soc2ntest(model::MOI.ModelLike, config::Config) - return _soc2test(model, config, true) +""" + test_conic_SecondOrderCone_Nonpositives( + model::MOI.ModelLike, + config::Config, + ) + +Test a SecondOrderCone with Nonpositives constraints. +""" +function test_conic_SecondOrderCone_Nonpositives( + model::MOI.ModelLike, + config::Config, +) + _test_conic_SecondOrderCone_helper_2(model, config, false) + return end -function soc2ptest(model::MOI.ModelLike, config::Config) - return _soc2test(model, config, false) + +function setup_test( + ::typeof(test_conic_SecondOrderCone_Nonpositives), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [-1 / √2, 1 / √2, 1.0], + (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => + [[√2, 1, -1]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[√2]], + (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => + [[-1.0]], + ), + ) + return end -function soc3test(model::MOI.ModelLike, config::Config) +""" + test_conic_SecondOrderCone_INFEASIBLE(model::MOI.ModelLike, config::Config) + +Problem SOC3 - Infeasible +min 0 +s.t. y ≥ 2 + x ≤ 1 + |y| ≤ x +in conic form: +min 0 +s.t. -2 + y ∈ R₊ + -1 + x ∈ R₋ + (x,y) ∈ SOC₂ +""" +function test_conic_SecondOrderCone_INFEASIBLE( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem SOC3 - Infeasible - # min 0 - # s.t. y ≥ 2 - # x ≤ 1 - # |y| ≤ x - # in conic form: - # min 0 - # s.t. -2 + y ∈ R₊ - # -1 + x ∈ R₋ - # (x,y) ∈ SOC₂ @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -1495,23 +2060,58 @@ function soc3test(model::MOI.ModelLike, config::Config) end # TODO test dual feasibility and objective sign end + return end -function soc4test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_conic_SecondOrderCone_INFEASIBLE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.INFEASIBLE_POINT, + MOI.INFEASIBILITY_CERTIFICATE, + ), + ) + return +end + +""" + test_conic_SecondOrderCone_out_of_order( + model::MOI.ModelLike, + config::Config, + ) + +Problem SOC4 +min 0x[1] - 2x[2] - 1x[3] + st x[1] == 1 (c1a) + x[2] - x[4] == 0 (c1b) + x[3] - x[5] == 0 (c1c) + x[1] >= ||(x[4],x[5])|| (c2) +in conic form: +min c^Tx +s.t. Ax + b ∈ {0}₃ + (x[1],x[4],x[5]) ∈ SOC₃ +Like SOCINT1 but with copies of variables and integrality relaxed +Tests out-of-order indices in cones +""" +function test_conic_SecondOrderCone_out_of_order( + model::MOI.ModelLike, + config::Config, +) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.SecondOrderCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem SOC4 - # min 0x[1] - 2x[2] - 1x[3] - # st x[1] == 1 (c1a) - # x[2] - x[4] == 0 (c1b) - # x[3] - x[5] == 0 (c1c) - # x[1] >= ||(x[4],x[5])|| (c2) - # in conic form: - # min c^Tx - # s.t. Ax + b ∈ {0}₃ - # (x[1],x[4],x[5]) ∈ SOC₃ - # Like SOCINT1 but with copies of variables and integrality relaxed - # Tests out-of-order indices in cones @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -1595,18 +2195,25 @@ function soc4test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return end -const soctests = Dict( - "soc1v" => soc1vtest, - "soc1f" => soc1ftest, - "soc2n" => soc2ntest, - "soc2p" => soc2ptest, - "soc3" => soc3test, - "soc4" => soc4test, +function setup_test( + ::typeof(test_conic_SecondOrderCone_out_of_order), + model::MOIU.MockOptimizer, + ::Config, ) - -@moitestset soc + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 2 / √5, 1 / √5, 2 / √5, 1 / √5], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => + [[-√5, -2.0, -1.0]], + ), + ) + return +end function _rotatedsoc1test(model::MOI.ModelLike, config::Config, abvars::Bool) atol = config.atol @@ -1737,10 +2344,16 @@ function _rotatedsoc1test(model::MOI.ModelLike, config::Config, abvars::Bool) end end -function rotatedsoc1vtest(model::MOI.ModelLike, config::Config) +function rotatedtest_conic_SecondOrderCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) return _rotatedsoc1test(model, config, true) end -function rotatedsoc1ftest(model::MOI.ModelLike, config::Config) +function rotatedtest_conic_SecondOrderCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) return _rotatedsoc1test(model, config, false) end @@ -1853,12 +2466,17 @@ function rotatedsoc2test(model::MOI.ModelLike, config::Config) vardual = MOI.get(model, MOI.ConstraintDual(), rsoc) @test vardual ≈ -y atol = atol rtol = rtol @test 2 * vardual[1] * vardual[2] ≥ vardual[3]^2 - atol - @test dot(b, y) > atol + @test b' * y > atol end end end -function rotatedsoc3test(model::MOI.ModelLike, config::Config; n = 2, ub = 3.0) +function rotatedtest_conic_SecondOrderCone_INFEASIBLE( + model::MOI.ModelLike, + config::Config; + n = 2, + ub = 3.0, +) atol = config.atol rtol = config.rtol # Problem SOCRotated3 @@ -2030,7 +2648,12 @@ function rotatedsoc3test(model::MOI.ModelLike, config::Config; n = 2, ub = 3.0) end end -function rotatedsoc4test(model::MOI.ModelLike, config::Config; n = 2, ub = 3.0) +function rotatedtest_conic_SecondOrderCone_out_of_order( + model::MOI.ModelLike, + config::Config; + n = 2, + ub = 3.0, +) atol = config.atol rtol = config.rtol # Problem SOCRotated4 @@ -2115,16 +2738,22 @@ function rotatedsoc4test(model::MOI.ModelLike, config::Config; n = 2, ub = 3.0) end const rsoctests = Dict( - "rotatedsoc1v" => rotatedsoc1vtest, - "rotatedsoc1f" => rotatedsoc1ftest, + "rotatedsoc1v" => rotatedtest_conic_SecondOrderCone_VectorOfVariables, + "rotatedsoc1f" => + rotatedtest_conic_SecondOrderCone_VectorAffineFunction, "rotatedsoc2" => rotatedsoc2test, - "rotatedsoc3" => rotatedsoc3test, - "rotatedsoc4" => rotatedsoc4test, + "rotatedsoc3" => rotatedtest_conic_SecondOrderCone_INFEASIBLE, + "rotatedsoc4" => rotatedtest_conic_SecondOrderCone_out_of_order, ) @moitestset rsoc -function _geomean1test(model::MOI.ModelLike, config::Config, vecofvars, n = 3) +function _geomean1test( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables, + n = 3, +) atol = config.atol rtol = config.rtol # Problem GeoMean1 @@ -2146,7 +2775,7 @@ function _geomean1test(model::MOI.ModelLike, config::Config, vecofvars, n = 3) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2169,7 +2798,7 @@ function _geomean1test(model::MOI.ModelLike, config::Config, vecofvars, n = 3) t = MOI.add_variable(model) x = MOI.add_variables(model, n) vov = MOI.VectorOfVariables([t; x]) - if vecofvars + if use_VectorOfVariables gmc = MOI.add_constraint(model, vov, MOI.GeometricMeanCone(n + 1)) else gmc = MOI.add_constraint( @@ -2187,7 +2816,7 @@ function _geomean1test(model::MOI.ModelLike, config::Config, vecofvars, n = 3) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, }(), @@ -2237,7 +2866,11 @@ function geomean1ftest(model::MOI.ModelLike, config::Config) end # addresses bug https://github.com/jump-dev/MathOptInterface.jl/pull/962 -function _geomean2test(model::MOI.ModelLike, config::Config, vecofvars) +function _geomean2test( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables, +) atol = config.atol rtol = config.rtol # Problem GeoMean2 @@ -2251,7 +2884,7 @@ function _geomean2test(model::MOI.ModelLike, config::Config, vecofvars) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2271,7 +2904,7 @@ function _geomean2test(model::MOI.ModelLike, config::Config, vecofvars) x = MOI.add_variables(model, n) @test MOI.get(model, MOI.NumberOfVariables()) == n + 1 vov = MOI.VectorOfVariables([t; x]) - if vecofvars + if use_VectorOfVariables gmc = MOI.add_constraint(model, vov, MOI.GeometricMeanCone(n + 1)) else gmc = MOI.add_constraint( @@ -2300,7 +2933,7 @@ function _geomean2test(model::MOI.ModelLike, config::Config, vecofvars) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, }(), @@ -2350,7 +2983,11 @@ function geomean2ftest(model::MOI.ModelLike, config::Config) end # Tests case where the dimension of the geometric mean cone is 2 -function _geomean3test(model::MOI.ModelLike, config::Config, vecofvars) +function _geomean3test( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables, +) atol = config.atol rtol = config.rtol # Problem GeoMean3 @@ -2364,7 +3001,7 @@ function _geomean3test(model::MOI.ModelLike, config::Config, vecofvars) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2383,7 +3020,7 @@ function _geomean3test(model::MOI.ModelLike, config::Config, vecofvars) x = MOI.add_variable(model) @test MOI.get(model, MOI.NumberOfVariables()) == 2 vov = MOI.VectorOfVariables([t; x]) - if vecofvars + if use_VectorOfVariables gmc = MOI.add_constraint(model, vov, MOI.GeometricMeanCone(2)) else gmc = MOI.add_constraint( @@ -2401,7 +3038,7 @@ function _geomean3test(model::MOI.ModelLike, config::Config, vecofvars) @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, }(), @@ -2461,7 +3098,19 @@ geomeantests = Dict( @moitestset geomean -function _exp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +function _test_conic_Exponential_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) + F = if use_VectorOfVariables + MOI.VectorOfVariables + else + MOI.VectorAffineFunction{Float64} + end + if !MOI.supports_constraint(model, F, MOI.ExponentialCone) + return + end atol = config.atol rtol = config.rtol # Problem EXP1 - ExpPrimal @@ -2475,7 +3124,7 @@ function _exp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2498,7 +3147,7 @@ function _exp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) v = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == 3 vov = MOI.VectorOfVariables(v) - if vecofvars + if use_VectorOfVariables vc = MOI.add_constraint(model, vov, MOI.ExponentialCone()) else vc = MOI.add_constraint( @@ -2556,18 +3205,90 @@ function _exp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) atol rtol = rtol end end + return +end + +""" + test_conic_Exponential_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test an exponential cone in standard conic form. +""" +function test_conic_Exponential_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_Exponential_helper(model, config, true) + return +end + +function setup_test( + ::typeof(test_conic_Exponential_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 2.0, 2exp(1 / 2)], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [1 + exp(1 / 2), 1 + exp(1 / 2) / 2], + ), + ) + return end -function exp1vtest(model::MOI.ModelLike, config::Config) - return _exp1test(model, config, true) +""" + test_conic_Exponential_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test an exponential cone in geometric conic form. +""" +function test_conic_Exponential_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_Exponential_helper(model, config, false) + return end -function exp1ftest(model::MOI.ModelLike, config::Config) - return _exp1test(model, config, false) + +function setup_test( + ::typeof(test_conic_Exponential_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 2.0, 2exp(1 / 2)], + (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => + [[-exp(1 / 2), -exp(1 / 2) / 2, 1.0]], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [1 + exp(1 / 2), 1 + exp(1 / 2) / 2], + ), + ) + return end -function exp2test(model::MOI.ModelLike, config::Config) - # Problem EXP2 - # A problem where ECOS was failing +""" + test_conic_Exponential_hard_2(model::MOI.ModelLike, config::Config) + +Test an exponential cone problem that ECOS failed. +""" +function test_conic_Exponential_hard_2(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.ExponentialCone, + ) + return + end atol = config.atol rtol = config.rtol @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -2728,11 +3449,49 @@ function exp2test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return +end + +function setup_test( + ::typeof(test_conic_Exponential_hard_2), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.0, -0.3, 0.0, exp(-0.3), exp(-0.3), exp(-0.3), 0.0, 1.0, 0.0], + (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [ + [-exp(-0.3) / 2, -1.3exp(-0.3) / 2, 0.5], + [-exp(-0.3) / 2, -1.3exp(-0.3) / 2, 0.5], + ], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0, exp(-0.3) * 0.3], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-exp(-0.3) * 0.3], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [ + [0.0, exp(-0.3), exp(-0.3) / 2], + [0.0, 0.0, exp(-0.3) / 2], + ], + ), + ) + return end -function exp3test(model::MOI.ModelLike, config::Config) - # Problem EXP3 - # A problem where ECOS was failing +""" + test_conic_Exponential_hard(model::MOI.ModelLike, config::Config) + +Test an exponential problem that ECOS failed. +""" +function test_conic_Exponential_hard(model::MOI.ModelLike, config::Config) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + MOI.ExponentialCone, + ) + return + end atol = config.atol rtol = config.rtol @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -2815,18 +3574,41 @@ function exp3test(model::MOI.ModelLike, config::Config) [-1.0, log(5) - 1, 1 / 5] atol = atol rtol = rtol end end + return end -exptests = Dict( - "exp1v" => exp1vtest, - "exp1f" => exp1ftest, - "exp2" => exp2test, - "exp3" => exp3test, +function setup_test( + ::typeof(test_conic_Exponential_hard), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [log(5), 5.0], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [0.0], + (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => + [[-1.0, log(5) - 1, 1 / 5]], + ), + ) + return +end -@moitestset exp - -function _dualexp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +function _test_conic_DualExponentialCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) + F = if use_VectorOfVariables + MOI.VectorOfVariables + else + MOI.VectorAffineFunction{Float64} + end + if !MOI.supports_constraint(model, F, MOI.DualExponentialCone) + return + end atol = config.atol rtol = config.rtol # Problem dual exp @@ -2842,7 +3624,7 @@ function _dualexp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2866,7 +3648,7 @@ function _dualexp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) x = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 5 vov = MOI.VectorOfVariables(v) - if vecofvars + if use_VectorOfVariables vc = MOI.add_constraint(model, vov, MOI.DualExponentialCone()) else vc = MOI.add_constraint( @@ -2941,33 +3723,104 @@ function _dualexp1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) atol rtol = rtol end end + return end -function dualexp1vtest(model::MOI.ModelLike, config::Config) - return _dualexp1test(model, config, true) +function test_conic_DualExponentialCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_DualExponentialCone_helper(model, config, true) + return end -function dualexp1ftest(model::MOI.ModelLike, config::Config) - return _dualexp1test(model, config, false) + +function setup_test( + ::typeof(test_conic_DualExponentialCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [ + -exp(1 / 2), + -exp(1 / 2) / 2, + 1.0, + 1 + exp(1 / 2), + 1 + exp(1 / 2) / 2, + ], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0, -2.0, -2exp(1 / 2)], + ), + ) + return end -dualexptests = Dict("dualexp1v" => dualexp1vtest, "dualexp1f" => dualexp1ftest) +function test_conic_DualExponentialCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_DualExponentialCone_helper(model, config, false) + return +end -@moitestset dualexp +function setup_test( + ::typeof(test_conic_DualExponentialCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [ + -exp(1 / 2), + -exp(1 / 2) / 2, + 1.0, + 1 + exp(1 / 2), + 1 + exp(1 / 2) / 2, + ], + (MOI.VectorAffineFunction{Float64}, MOI.DualExponentialCone) => + [[1.0, 2.0, 2exp(1 / 2)]], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0, -2.0, -2exp(1 / 2)], + ), + ) + return +end -function _pow1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) +""" +``` +max z + st x^0.9 * y^(0.1) >= |z| (i.e (x, y, z) are in the 3d power cone with a=0.9) + x == 2 + y == 1 +``` +Dual +``` +min -2α - β + st (u/0.9)^0.9 (v/0.1)^0.1 >= |w| + u + α = 0 + v + β = 0 + w = -1 +``` +""" +function _test_conic_PowerCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, +) + F = if use_VectorOfVariables + MOI.VectorOfVariables + else + MOI.VectorAffineFunction{Float64} + end + if !MOI.supports_constraint(model, F, MOI.PowerCone{Float64}) + return + end atol = config.atol rtol = config.rtol - # Problem POW1 - # max z - # st x^0.9 * y^(0.1) >= |z| (i.e (x, y, z) are in the 3d power cone with a=0.9) - # x == 2 - # y == 1 - # Dual - # min -2α - β - # st (u/0.9)^0.9 (v/0.1)^0.1 >= |w| - # u + α = 0 - # v + β = 0 - # w = -1 a = 0.9 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( @@ -2975,7 +3828,7 @@ function _pow1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -2998,7 +3851,7 @@ function _pow1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) v = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == 3 vov = MOI.VectorOfVariables(v) - if vecofvars + if use_VectorOfVariables vc = MOI.add_constraint(model, vov, MOI.PowerCone(a)) else vc = MOI.add_constraint( @@ -3055,49 +3908,116 @@ function _pow1test(model::MOI.ModelLike, config::Config, vecofvars::Bool) atol rtol = rtol end end + return +end + +function test_conic_PowerCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_PowerCone_helper(model, config, true) + return end -function pow1vtest(model::MOI.ModelLike, config::Config) - return _pow1test(model, config, true) +function setup_test( + ::typeof(test_conic_PowerCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + u_value = 0.839729692 + v_value = 2^0.9 - 2 * u_value + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [2.0, 1.0, 2^0.9], + (MOI.VectorOfVariables, MOI.PowerCone{Float64}) => + [[u_value, v_value, -1]], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-u_value, -v_value], + ), + ) + return end -function pow1ftest(model::MOI.ModelLike, config::Config) - return _pow1test(model, config, false) + +function test_conic_PowerCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_PowerCone_helper(model, config, false) + return end -powtests = Dict("pow1v" => pow1vtest, "pow1f" => pow1ftest) +function setup_test( + ::typeof(test_conic_PowerCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + u_value = 0.839729692 + v_value = 2^0.9 - 2 * u_value + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [2.0, 1.0, 2^0.9], + (MOI.VectorAffineFunction{Float64}, MOI.PowerCone{Float64}) => + [[u_value, v_value, -1]], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-u_value, -v_value], + ), + ) + return +end -@moitestset pow +""" + _test_conic_DualPowerCone_helper( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool; + exponent::Float64 = 0.9, + ) -function _dualpow1test( +Problem dual POW1 +``` +min -x_1 - x_2 + st x_1 + u == 0 + x_2 + v == 0 + w == 1 + (u, v, w) ∈ DualPowerCone(exponent) +``` +By the Weighted AM–GM inequality, you have +0.9a + 0.1b >= a^0.9 b^0.1 +with equality if and only if a == b +here taking a = u/0.9 and b = v/0.1, we have +u + v >= (u/0.9)^0.9 (v/0.1)^0.1 +with equality if and only if u/0.9 == v/0.1. +Here the best you can do is u + v == 1 and for that inequality must hold so u = 9v +hence you get v = 0.1 and u = 0.9. +The same works for other values of exponent as key word argument +""" +function _test_conic_DualPowerCone_helper( model::MOI.ModelLike, config::Config, - vecofvars::Bool; + use_VectorOfVariables::Bool; exponent::Float64 = 0.9, ) + F = if use_VectorOfVariables + MOI.VectorOfVariables + else + MOI.VectorAffineFunction{Float64} + end + if !MOI.supports_constraint(model, F, MOI.DualPowerCone{Float64}) + return + end atol = config.atol rtol = config.rtol - # Problem dual POW1 - # min -x_1 - x_2 - # st x_1 + u == 0 - # x_2 + v == 0 - # w == 1 - # (u, v, w) ∈ DualPowerCone(exponent) - # By the Weighted AM–GM inequality, you have - # 0.9a + 0.1b >= a^0.9 b^0.1 - # with equality if and only if a == b - # here taking a = u/0.9 and b = v/0.1, we have - # u + v >= (u/0.9)^0.9 (v/0.1)^0.1 - # with equality if and only if u/0.9 == v/0.1. - # Here the best you can do is u + v == 1 and for that inequality must hold so u = 9v - # hence you get v = 0.1 and u = 0.9. - # The same works for other values of exponent as key word argument @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint( model, MOI.VectorOfVariables, @@ -3121,7 +4041,7 @@ function _dualpow1test( x = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 5 vov = MOI.VectorOfVariables(v) - if vecofvars + if use_VectorOfVariables vc = MOI.add_constraint(model, vov, MOI.DualPowerCone(exponent)) else vc = MOI.add_constraint( @@ -3194,27 +4114,100 @@ function _dualpow1test( rtol end end + return +end + +""" + test_conic_DualPowerCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test DualPowerCone in the standard conic form. +""" +function test_conic_DualPowerCone_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_conic_DualPowerCone_helper(model, config, true) + return end -function dualpow1vtest(model::MOI.ModelLike, config::Config) - return _dualpow1test(model, config, true) +function setup_test( + ::typeof(test_conic_DualPowerCone_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.9, 0.1, 1.0, -0.9, -0.1], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0, -1.0, 1.0], + ), + ) + return end -function dualpow1ftest(model::MOI.ModelLike, config::Config) - return _dualpow1test(model, config, false) + +""" + test_conic_DualPowerCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test DualPowerCone in the geometric conic form. +""" +function test_conic_DualPowerCone_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_conic_DualPowerCone_helper(model, config, false) + return end -dualpowtests = Dict("dualpow1v" => dualpow1vtest, "dualpow1f" => dualpow1ftest) +function setup_test( + ::typeof(test_conic_DualPowerCone_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.9, 0.1, 1.0, -0.9, -0.1], + (MOI.VectorAffineFunction{Float64}, MOI.DualPowerCone{Float64}) => [[1.0, 1.0, -1.0]], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.0, -1.0, 1.0], + ), + ) + return +end -@moitestset dualpow +""" + test_conic_RelativeEntropyCone(model::MOI.ModelLike, config::Config) -function relentr1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +min u + st u >= 2*log(2/1) + 3*log(3/5) (i.e. (u, 1, 5, 2, 3) in RelativeEntropyCone(5)) +Optimal solution is: +u = 2*log(2/1) + 3*log(3/5) ≈ -0.1461825 +``` +""" +function test_conic_RelativeEntropyCone( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.RelativeEntropyCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem RelEntr1 - # min u - # st u >= 2*log(2/1) + 3*log(3/5) (i.e. (u, 1, 5, 2, 3) in RelativeEntropyCone(5)) - # Optimal solution is: - # u = 2*log(2/1) + 3*log(3/5) ≈ -0.1461825 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -3266,20 +4259,51 @@ function relentr1test(model::MOI.ModelLike, config::Config) rtol end end + return end -relentrtests = Dict("relentr1" => relentr1test) +function setup_test( + ::typeof(test_conic_RelativeEntropyCone), + model::MOIU.MockOptimizer, + ::Config, +) + u_opt = 2 * log(2 / 1) + 3 * log(3 / 5) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [u_opt], + (MOI.VectorAffineFunction{Float64}, MOI.RelativeEntropyCone) => + [[1, 2, 0.6, log(0.5) - 1, log(5 / 3) - 1]], + ), + ) + return +end -@moitestset relentr +""" + test_conic_NormSpectralCone(model::MOI.ModelLike, config::Config) -function normspec1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +min t + st t >= sigma_1([1 1 0; 1 -1 1]) (i.e (t, 1, 1, 1, -1, 0, 1]) is in NormSpectralCone(2, 3)) +Singular values are [sqrt(3), sqrt(2)], so optimal solution is: +t = sqrt(3) +``` +""" +function test_conic_NormSpectralCone( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.NormSpectralCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem NormSpec1 - # min t - # st t >= sigma_1([1 1 0; 1 -1 1]) (i.e (t, 1, 1, 1, -1, 0, 1]) is in NormSpectralCone(2, 3)) - # Singular values are [sqrt(3), sqrt(2)], so optimal solution is: - # t = sqrt(3) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -3332,16 +4356,51 @@ function normspec1test(model::MOI.ModelLike, config::Config) rtol end end + return +end + +function setup_test( + ::typeof(test_conic_NormSpectralCone), + model::MOIU.MockOptimizer, + ::Config, +) + invrt3 = inv(sqrt(3)) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [sqrt(3)], + (MOI.VectorAffineFunction{Float64}, MOI.NormSpectralCone) => + [Float64[1, 0, -invrt3, 0, invrt3, 0, -invrt3]], + ), + ) + return end -function normspec2test(model::MOI.ModelLike, config::Config) +""" + test_conic_NormSpectralCone_2(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +min t + st t >= sigma_1([1 1; 1 -1; 0 1]) (i.e (t, 1, 1, 0, 1, -1, 1]) is in NormSpectralCone(3, 2)) +Singular values are [sqrt(3), sqrt(2)], so optimal solution is: +t = sqrt(3) +``` +""" +function test_conic_NormSpectralCone_2( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.NormSpectralCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem NormSpec2 - # min t - # st t >= sigma_1([1 1; 1 -1; 0 1]) (i.e (t, 1, 1, 0, 1, -1, 1]) is in NormSpectralCone(3, 2)) - # Singular values are [sqrt(3), sqrt(2)], so optimal solution is: - # t = sqrt(3) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -3394,20 +4453,51 @@ function normspec2test(model::MOI.ModelLike, config::Config) rtol end end + return end -normspectests = Dict("normspec1" => normspec1test, "normspec2" => normspec2test) +function setup_test( + ::typeof(test_conic_NormSpectralCone_2), + model::MOIU.MockOptimizer, + ::Config, +) + invrt3 = inv(sqrt(3)) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [sqrt(3)], + (MOI.VectorAffineFunction{Float64}, MOI.NormSpectralCone) => + [Float64[1, 0, 0, 0, -invrt3, invrt3, -invrt3]], + ), + ) + return +end -@moitestset normspec +""" + test_conic_NormNuclearCone(model::MOI.ModelLike, config::Config) -function normnuc1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +min t + st t >= sum_i sigma_i([1 1 0; 1 -1 1]) (i.e (t, 1, 1, 1, -1, 0, 1]) is in NormNuclearCone(2, 3)) +Singular values are [sqrt(3), sqrt(2)], so optimal solution is: +t = sqrt(3) + sqrt(2) +``` +""" +function test_conic_NormNuclearCone( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.NormNuclearCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem NormNuc1 - # min t - # st t >= sum_i sigma_i([1 1 0; 1 -1 1]) (i.e (t, 1, 1, 1, -1, 0, 1]) is in NormNuclearCone(2, 3)) - # Singular values are [sqrt(3), sqrt(2)], so optimal solution is: - # t = sqrt(3) + sqrt(2) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -3463,16 +4553,52 @@ function normnuc1test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return +end + +function setup_test( + ::typeof(test_conic_NormNuclearCone), + model::MOIU.MockOptimizer, + ::Config, +) + invrt2 = inv(sqrt(2)) + invrt3 = inv(sqrt(3)) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [sqrt(2) + sqrt(3)], + (MOI.VectorAffineFunction{Float64}, MOI.NormNuclearCone) => + [Float64[1, -invrt2, -invrt3, -invrt2, invrt3, 0, -invrt3]], + ), + ) + return end -function normnuc2test(model::MOI.ModelLike, config::Config) +""" + test_conic_NormNuclearCone_2(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +min t + st t >= sum_i sigma_i([1 1; 1 -1; 0 1]) (i.e (t, 1, 1, 0, 1, -1, 1]) is in NormNuclearCone(3, 2)) +Singular values are [sqrt(3), sqrt(2)], so optimal solution is: +t = sqrt(3) + sqrt(2) +``` +""" +function test_conic_NormNuclearCone_2( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.NormNuclearCone, + ) + return + end atol = config.atol rtol = config.rtol - # Problem NormNuc2 - # min t - # st t >= sum_i sigma_i([1 1; 1 -1; 0 1]) (i.e (t, 1, 1, 0, 1, -1, 1]) is in NormNuclearCone(3, 2)) - # Singular values are [sqrt(3), sqrt(2)], so optimal solution is: - # t = sqrt(3) + sqrt(2) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports(model, MOI.ObjectiveFunction{MOI.SingleVariable}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -3528,15 +4654,31 @@ function normnuc2test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return end -normnuctests = Dict("normnuc1" => normnuc1test, "normnuc2" => normnuc2test) - -@moitestset normnuc +function setup_test( + ::typeof(test_conic_NormNuclearCone_2), + model::MOIU.MockOptimizer, + ::Config, +) + invrt2 = inv(sqrt(2)) + invrt3 = inv(sqrt(3)) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [sqrt(2) + sqrt(3)], + (MOI.VectorAffineFunction{Float64}, MOI.NormNuclearCone) => + [Float64[1, -invrt2, -invrt2, 0, -invrt3, invrt3, -invrt3]], + ), + ) + return +end function _psd0test( model::MOI.ModelLike, - vecofvars::Bool, + use_VectorOfVariables::Bool, psdcone, config::Config, ) @@ -3557,7 +4699,7 @@ function _psd0test( MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint(model, MOI.VectorOfVariables, psdcone) else @test MOI.supports_constraint( @@ -3576,7 +4718,7 @@ function _psd0test( X = MOI.add_variables(model, square ? 4 : 3) @test MOI.get(model, MOI.NumberOfVariables()) == (square ? 4 : 3) vov = MOI.VectorOfVariables(X) - if vecofvars + if use_VectorOfVariables cX = MOI.add_constraint(model, vov, psdcone(2)) else cX = MOI.add_constraint( @@ -3594,7 +4736,7 @@ function _psd0test( @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, psdcone, }(), @@ -3659,7 +4801,7 @@ end function _psd1test( model::MOI.ModelLike, - vecofvars::Bool, + use_VectorOfVariables::Bool, psdcone, config::Config, ) @@ -3746,7 +4888,7 @@ function _psd1test( MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, ) - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint(model, MOI.VectorOfVariables, psdcone) else @test MOI.supports_constraint( @@ -3767,7 +4909,7 @@ function _psd1test( x = MOI.add_variables(model, 3) @test MOI.get(model, MOI.NumberOfVariables()) == (square ? 12 : 9) vov = MOI.VectorOfVariables(X) - if vecofvars + if use_VectorOfVariables cX = MOI.add_constraint(model, vov, psdcone(3)) else cX = MOI.add_constraint( @@ -3818,7 +4960,7 @@ function _psd1test( @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, psdcone, }(), @@ -4173,29 +5315,46 @@ const sdptests = Dict("psdt" => psdttest, "psds" => psdstest) @moitestset sdp true -function _det1test( +""" + _test_det_cone_helper_ellipsoid( + model::MOI.ModelLike, + config::Config, + use_VectorOfVariables::Bool, + detcone, + ) + +We look for an ellipsoid x^T P x ≤ 1 contained in the square. +Let Q = inv(P) (x^T Q x ≤ 1 is its polar ellipsoid), we have +``` +max t + t <= log det Q (or t <= (det Q)^(1/n)) + Q22 ≤ 1 + _________ + | | + | | +-Q11 ≥ -1 | + | Q11 ≤ 1 + | | + |_________| + -Q22 ≥ -1 +``` +""" +function _test_det_cone_helper_ellipsoid( model::MOI.ModelLike, config::Config, - vecofvars::Bool, + use_VectorOfVariables::Bool, detcone, ) + F = + use_VectorOfVariables ? MOI.VectorOfVariables : + MOI.VectorAffineFunction{Float64} + if !MOI.supports_constraint(model, F, detcone) + return + end atol = config.atol rtol = config.rtol square = detcone == MOI.LogDetConeSquare || detcone == MOI.RootDetConeSquare use_logdet = detcone == MOI.LogDetConeTriangle || detcone == MOI.LogDetConeSquare - # We look for an ellipsoid x^T P x ≤ 1 contained in the square. - # Let Q = inv(P) (x^T Q x ≤ 1 is its polar ellipsoid), we have - # max t - # t <= log det Q (or t <= (det Q)^(1/n)) - # Q22 ≤ 1 - # _________ - # | | - # | | - # -Q11 ≥ -1 | + | Q11 ≤ 1 - # | | - # |_________| - # -Q22 ≥ -1 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -4209,7 +5368,7 @@ function _det1test( MOI.EqualTo{Float64}, ) end - if vecofvars + if use_VectorOfVariables @test MOI.supports_constraint(model, MOI.VectorOfVariables, detcone) else @test MOI.supports_constraint( @@ -4237,7 +5396,7 @@ function _det1test( else vov = MOI.VectorOfVariables([t; Q]) end - if vecofvars + if use_VectorOfVariables cX = MOI.add_constraint(model, vov, detcone(2)) else cX = MOI.add_constraint( @@ -4261,7 +5420,7 @@ function _det1test( @test MOI.get( model, MOI.NumberOfConstraints{ - vecofvars ? MOI.VectorOfVariables : + use_VectorOfVariables ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, detcone, }(), @@ -4329,32 +5488,34 @@ function _det1test( end end -function logdett1vtest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, true, MOI.LogDetConeTriangle) -end -function logdett1ftest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, false, MOI.LogDetConeTriangle) -end -function logdets1vtest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, true, MOI.LogDetConeSquare) -end -function logdets1ftest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, false, MOI.LogDetConeSquare) -end +""" + _test_det_cone_helper(model::MOI.ModelLike, config::Config, detcone) -function _det2test(model::MOI.ModelLike, config::Config, detcone) +A helper function for testing {Log,Root}DetCone{Square,Triangle}. + +We find logdet or rootdet of a symmetric PSD matrix: +``` +mat = |3 2 1| + |2 2 1| + |1 1 3| +det(mat) = 5, so: +rootdet(mat) ≈ 1.709976 +logdet(mat) ≈ 1.609438 +``` +""" +function _test_det_cone_helper(model::MOI.ModelLike, config::Config, detcone) + if !MOI.supports_constraint( + model, + MOI.VectorAffineFunction{Float64}, + detcone, + ) + return + end atol = config.atol rtol = config.rtol square = detcone == MOI.LogDetConeSquare || detcone == MOI.RootDetConeSquare use_logdet = detcone == MOI.LogDetConeTriangle || detcone == MOI.LogDetConeSquare - # We find logdet or rootdet of a symmetric PSD matrix: - # mat = |3 2 1| - # |2 2 1| - # |1 1 3| - # det(mat) = 5, so: - # rootdet(mat) ≈ 1.709976 - # logdet(mat) ≈ 1.609438 mat = Float64[3 2 1; 2 2 1; 1 1 3] matL = Float64[3, 2, 2, 1, 1, 3] @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -4418,90 +5579,421 @@ function _det2test(model::MOI.ModelLike, config::Config, detcone) end end -function logdett2test(model::MOI.ModelLike, config::Config) - return _det2test(model, config, MOI.LogDetConeTriangle) +""" + test_conic_LogDetConeTriangle_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with LogDetConeTriangle. +""" +function test_conic_LogDetConeTriangle_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid(model, config, true, MOI.LogDetConeTriangle) + return +end + +function setup_test( + ::typeof(test_conic_LogDetConeTriangle_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 1, 0, 1, 1], + (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[1, 1]], + (MOI.VectorOfVariables, MOI.LogDetConeTriangle) => + [[-1, -2, 1, 0, 1]], + ), + ) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = flag +end + +""" + test_conic_LogDetConeTriangle_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with LogDetConeTriangle. +""" +function test_conic_LogDetConeTriangle_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid( + model, + config, + false, + MOI.LogDetConeTriangle, + ) + return +end + +function setup_test( + ::typeof(test_conic_LogDetConeTriangle_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 1, 0, 1, 1], + (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[1, 1]], + (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle) => + [[-1, -2, 1, 0, 1]], + ), + ) + return +end + +""" + test_conic_LogDetConeSquare_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with LogDetConeSquare. +""" +function test_conic_LogDetConeSquare_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid(model, config, true, MOI.LogDetConeSquare) + return +end + +function setup_test( + ::typeof(test_conic_LogDetConeSquare_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 1, 0, 0, 1, 1], + (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[1, 1]], + (MOI.VectorOfVariables, MOI.LogDetConeSquare) => + [[-1, -2, 1, 0, 0, 1]], + ), + ) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = flag end -function logdets2test(model::MOI.ModelLike, config::Config) - return _det2test(model, config, MOI.LogDetConeSquare) + +""" + test_conic_LogDetConeSquare_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with LogDetConeSquare. +""" +function test_conic_LogDetConeSquare_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid(model, config, false, MOI.LogDetConeSquare) + return end -const logdetttests = Dict( - "logdett1v" => logdett1vtest, - "logdett1f" => logdett1ftest, - "logdett2" => logdett2test, +function setup_test( + ::typeof(test_conic_LogDetConeSquare_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 1, 0, 0, 1, 1], + (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[1, 1]], + (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeSquare) => + [[-1, -2, 1, 0, 0, 1]], + ), + ) + return +end -@moitestset logdett +""" + test_conic_RootDetConeTriangle_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) -const logdetstests = Dict( - "logdets1v" => logdets1vtest, - "logdets1f" => logdets1ftest, - "logdets2" => logdets2test, +Test a problem with RootDetConeTriangle. +""" +function test_conic_RootDetConeTriangle_VectorOfVariables( + model::MOI.ModelLike, + config::Config, ) + _test_det_cone_helper_ellipsoid( + model, + config, + true, + MOI.RootDetConeTriangle, + ) + return +end -@moitestset logdets +function setup_test( + ::typeof(test_conic_RootDetConeTriangle_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 1, 0, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[0.5, 0.5]], + (MOI.VectorOfVariables, MOI.RootDetConeTriangle) => + [[-1.0, 0.5, 0.0, 0.5]], + ), + ) + return +end -const logdettests = Dict("logdett" => logdetttest, "logdets" => logdetstest) +""" + test_conic_RootDetConeTriangle_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) -@moitestset logdet true +Test a problem with RootDetConeTriangle. +""" +function test_conic_RootDetConeTriangle_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid( + model, + config, + false, + MOI.RootDetConeTriangle, + ) + return +end -function rootdett1vtest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, true, MOI.RootDetConeTriangle) +function setup_test( + ::typeof(test_conic_RootDetConeTriangle_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 1, 0, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[0.5, 0.5]], + (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle) => + [[-1.0, 0.5, 0.0, 0.5]], + ), + ) + return end -function rootdett1ftest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, false, MOI.RootDetConeTriangle) + +""" + test_conic_RootDetConeSquare_VectorOfVariables( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with RootDetConeSquare. +""" +function test_conic_RootDetConeSquare_VectorOfVariables( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid(model, config, true, MOI.RootDetConeSquare) + return end -function rootdets1vtest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, true, MOI.RootDetConeSquare) + +function setup_test( + ::typeof(test_conic_RootDetConeSquare_VectorOfVariables), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 1, 0, 0, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[0.5, 0.5]], + (MOI.VectorOfVariables, MOI.RootDetConeSquare) => + [[-1.0, 0.5, 0.0, 0.0, 0.5]], + ), + ) + return end -function rootdets1ftest(model::MOI.ModelLike, config::Config) - return _det1test(model, config, false, MOI.RootDetConeSquare) + +""" + test_conic_RootDetConeSquare_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, + ) + +Test a problem with RootDetConeSquare. +""" +function test_conic_RootDetConeSquare_VectorAffineFunction( + model::MOI.ModelLike, + config::Config, +) + _test_det_cone_helper_ellipsoid(model, config, false, MOI.RootDetConeSquare) + return end -function rootdett2test(model::MOI.ModelLike, config::Config) - return _det2test(model, config, MOI.RootDetConeTriangle) + +function setup_test( + ::typeof(test_conic_RootDetConeSquare_VectorAffineFunction), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 1, 0, 0, 1], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [[0.5, 0.5]], + (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeSquare) => + [[-1.0, 0.5, 0.0, 0.0, 0.5]], + ), + ) + return end -function rootdets2test(model::MOI.ModelLike, config::Config) - return _det2test(model, config, MOI.RootDetConeSquare) + +""" + test_conic_LogDetConeTriangle(model::MOI.ModelLike, config::Config) + +Test a problem with LogDetConeTriangle. +""" +function test_conic_LogDetConeTriangle(model::MOI.ModelLike, config::Config) + _test_det_cone_helper(model, config, MOI.LogDetConeTriangle) + return end -const rootdetttests = Dict( - "rootdett1v" => rootdett1vtest, - "rootdett1f" => rootdett1ftest, - "rootdett2" => rootdett2test, +function setup_test( + ::typeof(test_conic_LogDetConeTriangle), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [log(5)], + (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle) => + [[-1, log(5) - 3, 1, -1, 1.6, 0, -0.2, 0.4]], + ), + ) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = flag +end + +""" + test_conic_LogDetConeSquare(model::MOI.ModelLike, config::Config) -@moitestset rootdett +Test a problem with LogDetConeSquare. +""" +function test_conic_LogDetConeSquare(model::MOI.ModelLike, config::Config) + _test_det_cone_helper(model, config, MOI.LogDetConeSquare) + return +end -const rootdetstests = Dict( - "rootdets1v" => rootdets1vtest, - "rootdets1f" => rootdets1ftest, - "rootdets2" => rootdets2test, +function setup_test( + ::typeof(test_conic_LogDetConeSquare), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [log(5)], + (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeSquare) => + [[-1, log(5) - 3, 1, -1, 0, -1, 1.6, -0.2, 0, -0.2, 0.4]], + ), + ) + return +end + +""" + test_conic_RootDetConeTriangle(model::MOI.ModelLike, config::Config) + +Test a problem with RootDetConeTriangle. +""" +function test_conic_RootDetConeTriangle(model::MOI.ModelLike, config::Config) + _test_det_cone_helper(model, config, MOI.RootDetConeTriangle) + return +end -@moitestset rootdets - -const rootdettests = - Dict("rootdett" => rootdetttest, "rootdets" => rootdetstest) - -@moitestset rootdet true - -const contconictests = Dict( - "lin" => lintest, - "norminf" => norminftest, - "normone" => normonetest, - "soc" => soctest, - "rsoc" => rsoctest, - "geomean" => geomeantest, - "exp" => exptest, - "dualexp" => dualexptest, - "pow" => powtest, - "dualpow" => dualpowtest, - "relentr" => relentrtest, - "normspec" => normspectest, - "normnuc" => normnuctest, - "sdp" => sdptest, - "logdet" => logdettest, - "rootdet" => rootdettest, +function setup_test( + ::typeof(test_conic_RootDetConeTriangle), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [5^inv(3)], + (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle) => + [vcat(-1, [1, -1, 1.6, 0, -0.2, 0.4] / 3 * (5^inv(3)))], + ), + ) + flag = model.eval_variable_constraint_dual + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = flag +end -@moitestset contconic true +""" + test_conic_RootDetConeSquare(model::MOI.ModelLike, config::Config) + +Test a problem with RootDetConeSquare. +""" +function test_conic_RootDetConeSquare(model::MOI.ModelLike, config::Config) + _test_det_cone_helper(model, config, MOI.RootDetConeSquare) + return +end + +function setup_test( + ::typeof(test_conic_RootDetConeSquare), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [5^inv(3)], + (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeSquare) => [ + vcat( + -1, + [1, -1, 0, -1, 1.6, -0.2, 0, -0.2, 0.4] / 3 * (5^inv(3)), + ), + ], + ), + ) + return +end diff --git a/src/Test/contlinear.jl b/src/Test/contlinear.jl index e524740993..f2beaae051 100644 --- a/src/Test/contlinear.jl +++ b/src/Test/contlinear.jl @@ -1,13 +1,16 @@ -# Continuous linear problems +""" + test_linear_integration(model::MOI.ModelLike, config::Config{T}) where {T} -# Basic solver, query, resolve -function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} +This test checks the integration of a sequence of common operations for linear +programs, including adding and deleting variables, and modifying variable +bounds and the objective function. +""" +function test_linear_integration( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # simple 2 variable, 1 constraint problem - # min -x - # st x + y <= 1 (x + y - 1 ∈ Nonpositives) - # x, y >= 0 (x, y ∈ Nonnegatives) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -31,9 +34,6 @@ function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} ) @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{T}) @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T}) - #@test MOI.get(model, MOI.SupportsAddConstraintAfterSolve()) - #@test MOI.get(model, MOI.SupportsAddVariableAfterSolve()) - #@test MOI.get(model, MOI.SupportsDeleteConstraint()) MOI.empty!(model) @test MOI.is_empty(model) v = MOI.add_variables(model, 2) @@ -69,7 +69,8 @@ function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(), ) == 2 end - # note: adding some redundant zero coefficients to catch solvers that don't handle duplicate coefficients correctly: + # Note: adding some redundant zero coefficients to catch solvers that don't + # handle duplicate coefficients correctly: objf = MOI.ScalarAffineFunction{T}( MOI.ScalarAffineTerm{ T, @@ -115,7 +116,6 @@ function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol = rtol - # reduced costs @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol = rtol @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 1 atol = atol rtol = @@ -299,7 +299,8 @@ function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} # s.t. x + y + z == 2 (c) # x,y >= 0, z = 0 MOI.delete(model, c) - # note: adding some redundant zero coefficients to catch solvers that don't handle duplicate coefficients correctly: + # Note: adding some redundant zero coefficients to catch solvers that don't + # handle duplicate coefficients correctly: cf = MOI.ScalarAffineFunction{T}( MOI.ScalarAffineTerm{ T, @@ -494,8 +495,59 @@ function linear1test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# add_variable (one by one) -function linear2test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_integration), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 0, 1], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-2], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-1, 0, 2]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 2, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 1, 0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-1.5], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [0.5], + ), + ) + return +end + +""" + test_linear_program( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a simple linear program. +""" +function test_linear_integration_2( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # Min -x @@ -577,7 +629,6 @@ function linear2test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol = rtol - # reduced costs @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol = rtol @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 1 atol = atol rtol = @@ -592,8 +643,39 @@ function linear2test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Issue #40 from Gurobi.jl -function linear3test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_integration_2), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1, 0], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [MOI.NONBASIC], + ], + var_basis = [MOI.BASIC, MOI.NONBASIC_AT_LOWER], + ), + ) + return +end + +""" + test_linear_inactive_bounds( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks solving a linear program with bounds that are not binding. +""" +function test_linear_inactive_bounds( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # min x @@ -714,8 +796,45 @@ function linear3test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Modify GreaterThan{T} and LessThan{T} sets as bounds -function linear4test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_inactive_bounds), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [3], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [MOI.NONBASIC], + ], + var_basis = [MOI.BASIC], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [MOI.BASIC], + ], + var_basis = [MOI.NONBASIC_AT_UPPER], + ), + ) + return +end + +""" + test_linear_LessThan_and_GreaterThan( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a linear program with variabless containing lower and upper bounds. +""" +function test_linear_LessThan_and_GreaterThan( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -794,12 +913,34 @@ function linear4test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Change coeffs, del constr, del var -function linear5test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_LessThan_and_GreaterThan), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), + ) + return +end + +""" + test_linear_integration_modification( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test a range of problem modifications for a linear program. +""" +function test_linear_integration_modification( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - #@test MOI.get(model, MOI.SupportsDeleteVariable()) - ##################################### # Start from simple LP # Solve it # Copy and solve again @@ -949,8 +1090,33 @@ function linear5test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Modify GreaterThan{T} and LessThan{T} sets as linear constraints -function linear6test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_integration_modification), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4 / 3, 4 / 3]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2]), + ) + return +end + +""" + test_linear_modify_GreaterThan_and_LessThan_constraints( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test modifying linear constraints. +""" +function test_linear_modify_GreaterThan_and_LessThan_constraints( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -1052,8 +1218,32 @@ function linear6test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Modify constants in Nonnegatives and Nonpositives -function linear7test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_modify_GreaterThan_and_LessThan_constraints), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), + ) + return +end + +""" + test_linear_VectorAffineFunction( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a linear program provided in vector-form. +""" +function test_linear_VectorAffineFunction( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # Min x - y @@ -1198,8 +1388,32 @@ function linear7test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# infeasible problem -function linear8atest(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_VectorAffineFunction), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), + ) + return +end + +""" + test_linear_INFEASIBLE( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving an infeasible linear program. +""" +function test_linear_INFEASIBLE( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # min x @@ -1277,8 +1491,43 @@ function linear8atest(model::MOI.ModelLike, config::Config{T}) where {T} end end -# unbounded problem -function linear8btest(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_INFEASIBLE), + mock::MOIU.MockOptimizer, + config::Config, +) + if config.infeas_certificates + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + tuple(), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], + ), + ) + else + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.INFEASIBLE), + ) + end + return +end + +""" + test_linear_DUAL_INFEASIBLE( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a problem that is dual infeasible (unbounded). +""" +function test_linear_DUAL_INFEASIBLE( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # min -x-y @@ -1347,8 +1596,36 @@ function linear8btest(model::MOI.ModelLike, config::Config{T}) where {T} end end -# unbounded problem with unique ray -function linear8ctest(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_DUAL_INFEASIBLE), + mock::MOIU.MockOptimizer, + config::Config, +) + primal_status = if config.infeas_certificates + MOI.INFEASIBILITY_CERTIFICATE + else + MOI.NO_SOLUTION + end + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE, primal_status), + ) + return +end + +""" + test_linear_DUAL_INFEASIBLE_2( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a linear program that is dual infeasible (unbounded). +""" +function test_linear_DUAL_INFEASIBLE_2( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # min -x-y @@ -1419,8 +1696,43 @@ function linear8ctest(model::MOI.ModelLike, config::Config{T}) where {T} end end -# add_constraints -function linear9test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_DUAL_INFEASIBLE_2), + mock::MOIU.MockOptimizer, + config::Config, +) + if config.infeas_certificates + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.DUAL_INFEASIBLE, + (MOI.INFEASIBILITY_CERTIFICATE, [1, 1]), + ), + ) + else + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE), + ) + end + return +end + +""" + test_linear_add_constraints( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a linear program constructed using the plural `add_constraints` +function. +""" +function test_linear_add_constraints( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # maximize 1000 x + 350 y @@ -1518,8 +1830,39 @@ function linear9test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# ranged constraints -function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_add_constraints), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [650 / 11, 400 / 11], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [MOI.NONBASIC, MOI.NONBASIC], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [MOI.BASIC], + ], + var_basis = [MOI.BASIC, MOI.BASIC], + ), + ) + return +end + +""" + test_linear_integration_Interval( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks a variety of properties of a model with +ScalarAffineFunction-in-Interval constraints. +""" +function test_linear_integration_Interval( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # maximize x + y @@ -1586,7 +1929,8 @@ function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} rtol end if config.basis - # There are multiple optimal bases. Either x or y can be in the optimal basis. + # There are multiple optimal bases. Either x or y can be in the + # optimal basis. @test ( MOI.get(model, MOI.VariableBasisStatus(), x) == MOI.BASIC || MOI.get(model, MOI.VariableBasisStatus(), y) == MOI.BASIC @@ -1623,7 +1967,8 @@ function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} rtol end if config.basis - # There are multiple optimal bases. Either x or y can be in the optimal basis." + # There are multiple optimal bases. Either x or y can be in the + # optimal basis. @test ( MOI.get(model, MOI.VariableBasisStatus(), x) == MOI.BASIC || MOI.get(model, MOI.VariableBasisStatus(), y) == MOI.BASIC @@ -1650,7 +1995,8 @@ function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 2 atol = atol rtol = rtol if config.basis - # There are multiple optimal bases. Either x or y can be in the optimal basis. + # There are multiple optimal bases. Either x or y can be in the + # optimal basis. @test ( MOI.get(model, MOI.VariableBasisStatus(), x) == MOI.BASIC || MOI.get(model, MOI.VariableBasisStatus(), y) == MOI.BASIC @@ -1683,7 +2029,8 @@ function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 12 atol = atol rtol = rtol if config.basis - # There are multiple optimal bases. Either x or y can be in the optimal basis. + # There are multiple optimal bases. Either x or y can be in the + # optimal basis. @test ( MOI.get(model, MOI.VariableBasisStatus(), x) == MOI.BASIC || MOI.get(model, MOI.VariableBasisStatus(), y) == MOI.BASIC @@ -1694,8 +2041,70 @@ function linear10test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# inactive ranged constraints -function linear10btest(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_integration_Interval), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [5.0, 5.0], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [MOI.NONBASIC_AT_UPPER], + ], + var_basis = [MOI.BASIC, MOI.BASIC], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [-1], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [2.5, 2.5], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [MOI.NONBASIC_AT_LOWER], + ], + var_basis = [MOI.BASIC, MOI.BASIC], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [1], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [1], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [MOI.NONBASIC_AT_LOWER], + ], + var_basis = [MOI.BASIC, MOI.BASIC], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [6.0, 6.0], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [-1], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [MOI.NONBASIC_AT_UPPER], + ], + var_basis = [MOI.BASIC, MOI.BASIC], + ), + ) + return +end + +""" + test_linear_Interval_inactive( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks linear programs with a non-binding +ScalarAffineFunction-in-Interval constraint. +""" +function test_linear_Interval_inactive( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # minimize x + y @@ -1775,41 +2184,72 @@ function linear10btest(model::MOI.ModelLike, config::Config{T}) where {T} end end -# changing constraint sense -function linear11test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_Interval_inactive), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.0, 0.0], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [MOI.BASIC], + ], + var_basis = [MOI.NONBASIC_AT_LOWER, MOI.NONBASIC_AT_LOWER], + (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => + [0], + ), + ) + return +end + +""" + test_linear_transform( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks calling `MOI.transform` to flip the sense of constraints. + +It starts with: +``` +min x + y +st x + y >= 1 + x + y >= 2 + sol: x+y = 2 (degenerate) +``` +with the dual problem: +``` +max w + 2z +st w + z == 1 + w + z == 1 + w, z >= 0 +sol: z = 1, w = 0 +``` +Then it tranforms problem into: +``` +min x + y +st x + y >= 1 + x + y <= 2 +sol: x+y = 1 (degenerate) +``` +with the dual problem: +``` +max w + 2z +st w + z == 1 + w + z == 1 + w >= 0, z <= 0 +sol: w = 1, z = 0 +``` +""" +function test_linear_transform( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # simple 2 variable, 1 constraint problem - # - # starts with - # - # min x + y - # st x + y >= 1 - # x + y >= 2 - # sol: x+y = 2 (degenerate) - # - # with dual - # - # max w + 2z - # st w + z == 1 - # w + z == 1 - # w, z >= 0 - # sol: z = 1, w = 0 - # - # tranforms problem into: - # - # min x + y - # st x + y >= 1 - # x + y <= 2 - # sol: x+y = 1 (degenerate) - # - # with dual - # - # max w + 2z - # st w + z == 1 - # w + z == 1 - # w >= 0, z <= 0 - # sol: w = 1, z = 0 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -1878,14 +2318,41 @@ function linear11test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# infeasible problem with 2 linear constraints -function linear12test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_transform), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5]), + ) + return +end + +""" + test_linear_INFEASIBLE_2( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving an infeasible linear program. + +The model is +``` + min x +s.t. 2x - 3y <= -7 + y <= 2 + x, y >= 0 +``` +""" +function test_linear_INFEASIBLE_2( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # min x - # s.t. 2x-3y <= -7 - # y <= 2 - # x,y >= 0 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -1946,7 +2413,6 @@ function linear12test(model::MOI.ModelLike, config::Config{T}) where {T} MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE_OR_UNBOUNDED if config.duals && config.infeas_certificates - # solver returned an infeasibility ray @test MOI.get(model, MOI.ResultCount()) >= 1 @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE @@ -1963,13 +2429,52 @@ function linear12test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# feasibility problem -function linear13test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_INFEASIBLE_2), + mock::MOIU.MockOptimizer, + config::Config, +) + if config.infeas_certificates + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + tuple(), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1, -1], + ), + ) + else + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, MOI.INFEASIBLE), + ) + end + return +end + +""" + test_linear_FEASIBILITY_SENSE( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +Test solving a linear program with no objective function. + +The problem is +``` +Find x, y +s.t. 2x + 3y >= 1 + x - y == 0 +``` +""" +function test_linear_FEASIBILITY_SENSE( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # find x, y - # s.t. 2x + 3y >= 1 - # x - y == 0 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports_constraint( model, @@ -2029,8 +2534,37 @@ function linear13test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# Deletion of vector of variables -function linear14test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_FEASIBILITY_SENSE), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1 / 5, 1 / 5], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [0], + ), + ) + return +end + +""" + test_linear_integration_delete_variables( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This function tests calling `delete(::ModelLike, ::Vector{VariableIndex})`. +""" +function test_linear_integration_delete_variables( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol # max x + 2y + 3z + 4 @@ -2121,7 +2655,6 @@ function linear14test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol = rtol - # reduced costs @test MOI.get(model, MOI.ConstraintDual(), clbx) ≈ 2 atol = atol rtol = rtol @test MOI.get(model, MOI.ConstraintDual(), clby) ≈ 0 atol = atol rtol = @@ -2161,20 +2694,70 @@ function linear14test(model::MOI.ModelLike, config::Config{T}) where {T} @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol = rtol - # reduced costs @test MOI.get(model, MOI.ConstraintDual(), clby) ≈ 0 atol = atol rtol = rtol end end end -# Empty vector affine function rows (LQOI Issue #48) -function linear15test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_integration_delete_variables), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0, 1 / 2, 1], + con_basis = [ + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [MOI.NONBASIC], + ], + var_basis = [ + MOI.NONBASIC_AT_LOWER, + MOI.BASIC, + MOI.NONBASIC_AT_UPPER, + ], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1], + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2, 0, 0], + (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1], + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => + [-1], + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0], + ), + ) + # test_linear_integration_delete_variables has double variable bounds for + # the z variable + mock.eval_variable_constraint_dual = false + function reset_function() + mock.eval_variable_constraint_dual = true + return + end + return reset_function +end + +""" + test_linear_VectorAffineFunction_empty_row( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks solving a problem given a `VectorAffineFunction` with an empty +row equivalent to `0 == 0`. + +Models not supporting `VectorAffineFunction`-in-`Zeros` should use a bridge. +""" +function test_linear_VectorAffineFunction_empty_row( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # minimize 0 - # s.t. 0 == 0 - # x == 1 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -2185,9 +2768,9 @@ function linear15test(model::MOI.ModelLike, config::Config{T}) where {T} MOI.empty!(model) @test MOI.is_empty(model) x = MOI.add_variables(model, 1) - # Create a VectorAffineFunction with two rows, but only - # one term, belonging to the second row. The first row, - # which is empty, is essentially a constraint that 0 == 0. + # Create a VectorAffineFunction with two rows, but only one term, belonging + # to the second row. The first row, which is empty, is essentially a + # constraint that 0 == 0. c = MOI.add_constraint( model, MOI.VectorAffineFunction{T}( @@ -2223,15 +2806,48 @@ function linear15test(model::MOI.ModelLike, config::Config{T}) where {T} end end -# This test can be passed by solvers that don't support VariablePrimalStart -# because copy_to drops start information with a warning. -function partial_start_test(model::MOI.ModelLike, config::Config{T}) where {T} +function setup_test( + ::typeof(test_linear_VectorAffineFunction_empty_row), + mock::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.0], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[0.0, 0.0]], + ), + ) + return +end + +""" + test_linear_VariablePrimalStart_partial( + model::MOI.ModelLike, + config::Config{T}, + ) where {T} + +This test checks solving a problem with a partial solution provided via +VariablePrimalStart. + +This test can be passed by solvers that don't support VariablePrimalStart +because copy_to drops start information with a warning. + +The problem is +``` + max 2x + y +s.t. x + y <= 1 + x, y >= 0 +``` +where `x` starts at `one(T)`. Start point for `y` is unspecified. +""" +function test_linear_VariablePrimalStart_partial( + model::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol - # maximize 2x + y - # s.t. x + y <= 1 - # x, y >= 0 - # x starts at one(T). Start point for y is unspecified. MOI.empty!(model) @test MOI.is_empty(model) x = MOI.add_variable(model) @@ -2263,26 +2879,14 @@ function partial_start_test(model::MOI.ModelLike, config::Config{T}) where {T} end end -const contlineartests = Dict( - "linear1" => linear1test, - "linear2" => linear2test, - "linear3" => linear3test, - "linear4" => linear4test, - "linear5" => linear5test, - "linear6" => linear6test, - "linear7" => linear7test, - "linear8a" => linear8atest, - "linear8b" => linear8btest, - "linear8c" => linear8ctest, - "linear9" => linear9test, - "linear10" => linear10test, - "linear10b" => linear10btest, - "linear11" => linear11test, - "linear12" => linear12test, - "linear13" => linear13test, - "linear14" => linear14test, - "linear15" => linear15test, - "partial_start" => partial_start_test, +function setup_test( + ::typeof(test_linear_VariablePrimalStart_partial), + mock::MOIU.MockOptimizer, + ::Config, ) - -@moitestset contlinear + MOIU.set_mock_optimize!( + mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0]), + ) + return +end diff --git a/src/Test/contquadratic.jl b/src/Test/contquadratic.jl index 1c019497a3..0fa2dcbe56 100644 --- a/src/Test/contquadratic.jl +++ b/src/Test/contquadratic.jl @@ -1,13 +1,17 @@ -# Continuous quadratic problems +""" + test_quadratic_integration(model::MOI.ModelLike, config::Config) -function qp1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +Min x^2 + xy + y^2 + yz + z^2 +st x + 2y + 3z >= 4 (c1) + x + y >= 1 (c2) + x, y, z in R +``` +""" +function test_quadratic_integration(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # homogeneous quadratic objective - # Min x^2 + xy + y^2 + yz + z^2 - # st x + 2y + 3z >= 4 (c1) - # x + y >= 1 (c2) - # x, y, z \in R @test MOI.supports_incremental_interface(model, false) #=copy_names=# MOI.supports( model, @@ -96,17 +100,40 @@ function qp1test(model::MOI.ModelLike, config::Config) rtol end end + return end -function qp2test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_quadratic_integration), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [4 / 7, 3 / 7, 6 / 7], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [5 / 7, 6 / 7], + ), + ) + return +end + +""" + test_quadratic_duplicate_terms(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +Min x^2 + xy + y^2 + yz + z^2 +st x + 2y + 3z >= 4 (c1) + x + y >= 1 (c2) + x, y, z in R +``` +""" +function test_quadratic_duplicate_terms(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # Same as `qp1` but with duplicate terms then change the objective and sense - # simple quadratic objective - # Min x^2 + xy + y^2 + yz + z^2 - # st x + 2y + 3z >= 4 (c1) - # x + y >= 1 (c2) - # x, y, z \in R @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -232,15 +259,45 @@ function qp2test(model::MOI.ModelLike, config::Config) rtol end end + return +end + +function setup_test( + ::typeof(test_quadratic_duplicate_terms), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [4 / 7, 3 / 7, 6 / 7], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [5 / 7, 6 / 7], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [4 / 7, 3 / 7, 6 / 7], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [10 / 7, 12 / 7], + ), + ) + return end -function qp3test(model::MOI.ModelLike, config::Config) +""" + test_quadratic_nonhomogeneous(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +minimize 2 x^2 + y^2 + xy + x + y + 1 + s.t. x, y >= 0 + x + y = 1 +``` +""" +function test_quadratic_nonhomogeneous(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # non-homogeneous quadratic objective - # minimize 2 x^2 + y^2 + xy + x + y + 1 - # s.t. x, y >= 0 - # x + y = 1 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -296,7 +353,8 @@ function qp3test(model::MOI.ModelLike, config::Config) rtol if config.duals @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT - # The dual constraint gives [λ_vc1 + λ_c1, λ_vc2 + λ_c1] = [4 1; 1 2] * x + [1, 1] = [11, 11] / 4 + # The dual constraint gives + # [λ_vc1 + λ_c1, λ_vc2 + λ_c1] = [4 1; 1 2] * x + [1, 1] = [11, 11] / 4 # since `vc1` and `vc2` are not active, `λ_vc1` and `λ_vc2` are # zero so `λ_c1 = 11/4`. @test MOI.get(model, MOI.ConstraintDual(), c1) ≈ 11 / 4 atol = atol rtol = @@ -312,7 +370,8 @@ function qp3test(model::MOI.ModelLike, config::Config) # s.t. x, y >= 0 # x + y = 1 # (x,y) = (1,0), obj = 3 - # First clear the objective, this is needed if a `Bridges.Objective.SlackBridge` is used. + # First clear the objective, this is needed if a + # `Bridges.Objective.SlackBridge` is used. MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) objf = @@ -343,26 +402,51 @@ function qp3test(model::MOI.ModelLike, config::Config) rtol end end + return end -const qptests = Dict("qp1" => qp1test, "qp2" => qp2test, "qp3" => qp3test) - -@moitestset qp +function setup_test( + ::typeof(test_quadratic_nonhomogeneous), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1 / 4, 3 / 4], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [11 / 4], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 0.0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => + [-2.0], + ), + ) + return +end -#= - Quadratically constrained (convex) programs -=# +""" + test_quadratic_constraint_integration(model::MOI.ModelLike, config::Config) -function qcp1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +Max x + y +st -x + y >= 0 (c1[1]) + x + y >= 0 (c1[2]) + x² + y <= 2 (c2) +Optimal solution +x = 1/2, y = 7/4 +``` +""" +function test_quadratic_constraint_integration( + model::MOI.ModelLike, + config::Config, +) atol = config.atol rtol = config.rtol - # quadratic constraint - # Max x + y - # st -x + y >= 0 (c1[1]) - # x + y >= 0 (c1[2]) - # x² + y <= 2 (c2) - # Optimal solution - # x = 1/2, y = 7/4 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -469,13 +553,40 @@ function qcp1test(model::MOI.ModelLike, config::Config) # @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT # # @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0.0 atol=atol rtol=rtol + return +end + +function setup_test( + ::typeof(test_quadratic_constraint_integration), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1 / 2, 7 / 4], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => + [zeros(2)], + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => + [-1.0], + ), + ) + return end -function qcp2test(model::MOI.ModelLike, config::Config) +""" + test_quadratic_constraint_basic(model::MOI.ModelLike, config::Config) + +Test the problem: +``` + Max x +s.t. x^2 <= 2 (c) +``` +""" +function test_quadratic_constraint_basic(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # Max x - # s.t. x^2 <= 2 (c) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -535,13 +646,44 @@ function qcp2test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return +end + +function setup_test( + ::typeof(test_quadratic_constraint_basic), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [√2], + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => + [-1 / (2 * √2)], + ), + ) + return end -function qcp3test(model::MOI.ModelLike, config::Config) +""" + test_quadratic_constraint_minimize( + model::MOI.ModelLike, + config::Config, + ) + +Test the problem: +``` + Min -x +s.t. x^2 <= 2 +``` +""" +function test_quadratic_constraint_minimize( + model::MOI.ModelLike, + config::Config, +) atol = config.atol rtol = config.rtol - # Min -x - # s.t. x^2 <= 2 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -604,9 +746,34 @@ function qcp3test(model::MOI.ModelLike, config::Config) atol rtol = rtol end end + return end -function _qcp4test(model::MOI.ModelLike, config::Config, less_than::Bool) +function setup_test( + ::typeof(test_quadratic_constraint_minimize), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [√2], + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => + [-1 / (2 * √2)], + ), + ) + return +end + +""" + +""" +function _test_quadratic_constraint_helper( + model::MOI.ModelLike, + config::Config, + less_than::Bool, +) atol = config.atol rtol = config.rtol # Max x @@ -687,35 +854,77 @@ function _qcp4test(model::MOI.ModelLike, config::Config, less_than::Bool) rtol end end + return end -function qcp4test(model::MOI.ModelLike, config::Config) - return _qcp4test(model, config, true) +function test_quadratic_constraint_LessThan( + model::MOI.ModelLike, + config::Config, +) + _test_quadratic_constraint_helper(model, config, true) + return end -function qcp5test(model::MOI.ModelLike, config::Config) - return _qcp4test(model, config, false) + +function setup_test( + ::typeof(test_quadratic_constraint_LessThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 1.0], + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => + [-1 / 3], + ), + ) + return end -const qcptests = Dict( - "qcp1" => qcp1test, - "qcp2" => qcp2test, - "qcp3" => qcp3test, - "qcp4" => qcp4test, - "qcp5" => qcp5test, +function test_quadratic_constraint_GreaterThan( + model::MOI.ModelLike, + config::Config, ) + _test_quadratic_constraint_helper(model, config, false) + return +end -@moitestset qcp +function setup_test( + ::typeof(test_quadratic_constraint_GreaterThan), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1.0, 1.0], + (MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}) => [1 / 3], + ), + ) + return +end -#= - Quadratically constrained (non-convex) programs -=# +""" + test_nonconvex_quadratic_constraint_integration( + model::MOI.ModelLike, + config::Config, + ) -function ncqcp1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +Max 2x + y +s.t. x * y <= 4 (c) + x, y >= 1 +``` +""" +function test_nonconvex_quadratic_constraint_integration( + model::MOI.ModelLike, + config::Config, +) atol = config.atol rtol = config.rtol - # Max 2x + y - # s.t. x * y <= 4 (c) - # x, y >= 1 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -780,15 +989,42 @@ function ncqcp1test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.ConstraintPrimal(), vc2) ≈ 1.0 atol = atol rtol = rtol end + return +end + +function setup_test( + ::typeof(test_nonconvex_quadratic_constraint_integration), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [4.0, 1.0], MOI.FEASIBLE_POINT), + ) + return end -function ncqcp2test(model::MOI.ModelLike, config::Config) +""" + test_nonconvex_quadratic_constraint_basic( + model::MOI.ModelLike, + config::Config, + ) + +Test the problem: +``` +Find x, y +s.t. x * y == 4 (c) + x * x == 4 (c2) + x, y >= 0 +``` +""" +function test_nonconvex_quadratic_constraint_basic( + model::MOI.ModelLike, + config::Config, +) atol = config.atol rtol = config.rtol - # Find x, y - # s.t. x * y == 4 (c) - # x * x == 4 (c2) - # x, y >= 0 @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports_constraint( model, @@ -847,23 +1083,36 @@ function ncqcp2test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.ConstraintPrimal(), vc2) ≈ 2.0 atol = atol rtol = rtol end + return end -const ncqcptests = Dict("ncqcp1" => ncqcp1test, "ncqcp2" => ncqcp2test) - -@moitestset ncqcp +function setup_test( + ::typeof(test_nonconvex_quadratic_constraint_basic), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [2.0, 2.0], MOI.FEASIBLE_POINT), + ) + return +end -#= - SOCP -=# +""" + test_socp_basic(model::MOI.ModelLike, config::Config) -function socp1test(model::MOI.ModelLike, config::Config) +Test the problem: +``` +min t +s.t. x + y >= 1 (c1) + x^2 + y^2 <= t^2 (c2) + t >= 0 (bound) +``` +""" +function test_socp_basic(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # min t - # s.t. x + y >= 1 (c1) - # x^2 + y^2 <= t^2 (c2) - # t >= 0 (bound) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -966,17 +1215,24 @@ function socp1test(model::MOI.ModelLike, config::Config) rtol end end + return end -const socptests = Dict("socp1" => socp1test) - -@moitestset socp - -const contquadratictests = Dict( - "qp" => qptest, - "qcp" => qcptest, - "ncqcp" => ncqcptest, - "socp" => socptest, +function setup_test( + ::typeof(test_socp_basic), + model::MOIU.MockOptimizer, + ::Config, ) - -@moitestset contquadratic true + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [1 / 2, 1 / 2, 1 / √2], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => + [1 / √2], + (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => + [-1 / √2], + ), + ) + return +end diff --git a/src/Test/intconic.jl b/src/Test/intconic.jl index b9ef229a08..4ddf7dfff1 100644 --- a/src/Test/intconic.jl +++ b/src/Test/intconic.jl @@ -1,13 +1,17 @@ -# Integer conic problems +""" + test_Integer_SecondOrderCone(model::MOI.ModelLike, config::Config) -function intsoc1test(model::MOI.ModelLike, config::Config) +Run an integration test on the problem: +``` + min - 2y - 1z +s.t. x == 1 + [x, y, z] in SecondOrderCone() + y, z in ZeroOne() +``` +""" +function test_Integer_SecondOrderCone(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # Problem SINTSOC1 - # min 0x - 2y - 1z - # st x == 1 - # x >= ||(y,z)|| - # (y,z) binary @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -87,10 +91,15 @@ function intsoc1test(model::MOI.ModelLike, config::Config) end end -const intsoctests = Dict("intsoc1" => intsoc1test) - -@moitestset intsoc - -const intconictests = Dict("intsoc" => intsoctest) - -@moitestset intconic true +function setup_test( + ::typeof(test_Integer_SecondOrderCone), + model::MOI.Utilities.MockOptimizer, + config::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1.0, 1.0, 0.0]), + ) + return +end diff --git a/src/Test/intlinear.jl b/src/Test/intlinear.jl index 852ae5e7a9..d8e425e4b9 100644 --- a/src/Test/intlinear.jl +++ b/src/Test/intlinear.jl @@ -1,17 +1,19 @@ -# MIP01 from CPLEX.jl -function int1test(model::MOI.ModelLike, config::Config) +""" + test_integer_integration(model::MOI.ModelLike, config::Config) + +Run an integration test on the MILP: +``` +maximize 1.1x + 2 y + 5 z +s.t. x + y + z <= 10 + x + 2 y + z <= 15 + x is continuous: 0 <= x <= 5 + y is integer: 0 <= y <= 10 + z is binary +``` +""" +function test_integer_integration(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # an example on mixed integer programming - # - # maximize 1.1x + 2 y + 5 z - # - # s.t. x + y + z <= 10 - # x + 2 y + z <= 15 - # - # x is continuous: 0 <= x <= 5 - # y is integer: 0 <= y <= 10 - # z is binary @test MOI.supports_incremental_interface(model, false) #=copy_names=# @test MOI.supports( model, @@ -132,251 +134,288 @@ function int1test(model::MOI.ModelLike, config::Config) # @test MOI.get(model, MOI.BarrierIterations()) >= 0 # @test MOI.get(model, MOI.NodeCount()) >= 0 end + return +end + +function setup_test( + ::typeof(test_integer_integration), + model::MOI.Utilities.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 20.0) + MOIU.mock_optimize!(mock, [4, 5, 1]) + end, + ) + return end -# sos from CPLEX.jl" begin -function int2test(model::MOI.ModelLike, config::Config) +""" + test_SOS1_integration(model::MOI.ModelLike, config::Config) + +Test Special Ordered Sets of type 1. +""" +function test_SOS1_integration(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - @testset "SOSI" begin - @test MOI.supports_incremental_interface(model, false) #=copy_names=# - @test MOI.supports( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - ) - @test MOI.supports(model, MOI.ObjectiveSense()) - @test MOI.supports_constraint( - model, - MOI.VectorOfVariables, - MOI.SOS1{Float64}, - ) - @test MOI.supports_constraint( - model, - MOI.SingleVariable, - MOI.LessThan{Float64}, - ) - MOI.empty!(model) - @test MOI.is_empty(model) - v = MOI.add_variables(model, 3) - @test MOI.get(model, MOI.NumberOfVariables()) == 3 - vc1 = MOI.add_constraint( - model, - MOI.SingleVariable(v[1]), - MOI.LessThan(1.0), - ) - @test vc1.value == v[1].value - vc2 = MOI.add_constraint( - model, - MOI.SingleVariable(v[2]), - MOI.LessThan(1.0), - ) - @test vc2.value == v[2].value - vc3 = MOI.add_constraint( - model, - MOI.SingleVariable(v[3]), - MOI.LessThan(2.0), - ) - @test vc3.value == v[3].value - c1 = MOI.add_constraint( - model, - MOI.VectorOfVariables([v[1], v[2]]), - MOI.SOS1([1.0, 2.0]), - ) - c2 = MOI.add_constraint( - model, - MOI.VectorOfVariables([v[1], v[3]]), - MOI.SOS1([1.0, 2.0]), - ) - if config.query_number_of_constraints - @test MOI.get( - model, - MOI.NumberOfConstraints{ - MOI.VectorOfVariables, - MOI.SOS1{Float64}, - }(), - ) == 2 - end - #= - To allow for permutations in the sets and variable vectors - we're going to sort according to the weights - =# - cs_sos = MOI.get(model, MOI.ConstraintSet(), c2) - cf_sos = MOI.get(model, MOI.ConstraintFunction(), c2) - p = sortperm(cs_sos.weights) - @test cs_sos.weights[p] ≈ [1.0, 2.0] atol = atol rtol = rtol - @test cf_sos.variables[p] == v[[1, 3]] - objf = MOI.ScalarAffineFunction( - MOI.ScalarAffineTerm.([2.0, 1.0, 1.0], v), - 0.0, - ) - MOI.set( + @test MOI.supports_incremental_interface(model, false) #=copy_names=# + @test MOI.supports( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) + @test MOI.supports(model, MOI.ObjectiveSense()) + @test MOI.supports_constraint( + model, + MOI.VectorOfVariables, + MOI.SOS1{Float64}, + ) + @test MOI.supports_constraint( + model, + MOI.SingleVariable, + MOI.LessThan{Float64}, + ) + MOI.empty!(model) + @test MOI.is_empty(model) + v = MOI.add_variables(model, 3) + @test MOI.get(model, MOI.NumberOfVariables()) == 3 + vc1 = MOI.add_constraint(model, MOI.SingleVariable(v[1]), MOI.LessThan(1.0)) + @test vc1.value == v[1].value + vc2 = MOI.add_constraint(model, MOI.SingleVariable(v[2]), MOI.LessThan(1.0)) + @test vc2.value == v[2].value + vc3 = MOI.add_constraint(model, MOI.SingleVariable(v[3]), MOI.LessThan(2.0)) + @test vc3.value == v[3].value + c1 = MOI.add_constraint( + model, + MOI.VectorOfVariables([v[1], v[2]]), + MOI.SOS1([1.0, 2.0]), + ) + c2 = MOI.add_constraint( + model, + MOI.VectorOfVariables([v[1], v[3]]), + MOI.SOS1([1.0, 2.0]), + ) + if config.query_number_of_constraints + @test MOI.get( model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - objf, - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - if config.solve - @test MOI.get(model, MOI.TerminationStatus()) == - MOI.OPTIMIZE_NOT_CALLED - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == - config.optimal_status - @test MOI.get(model, MOI.ResultCount()) >= 1 - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 3 atol = atol rtol = - rtol - @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [0, 1, 2] atol = - atol rtol = rtol - end - MOI.delete(model, c1) - MOI.delete(model, c2) - if config.solve - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == - config.optimal_status - @test MOI.get(model, MOI.ResultCount()) >= 1 - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 5 atol = atol rtol = - rtol - @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 1, 2] atol = - atol rtol = rtol - end + MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.SOS1{Float64}}(), + ) == 2 end - @testset "SOSII" begin - @test MOI.supports_incremental_interface(model, false) #=copy_names=# - @test MOI.supports( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - ) - @test MOI.supports(model, MOI.ObjectiveSense()) - @test MOI.supports_constraint( - model, - MOI.VectorOfVariables, - MOI.SOS1{Float64}, - ) - @test MOI.supports_constraint( - model, - MOI.VectorOfVariables, - MOI.SOS2{Float64}, - ) - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) - @test MOI.supports_constraint( + #= + To allow for permutations in the sets and variable vectors + we're going to sort according to the weights + =# + cs_sos = MOI.get(model, MOI.ConstraintSet(), c2) + cf_sos = MOI.get(model, MOI.ConstraintFunction(), c2) + p = sortperm(cs_sos.weights) + @test cs_sos.weights[p] ≈ [1.0, 2.0] atol = atol rtol = rtol + @test cf_sos.variables[p] == v[[1, 3]] + objf = + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0, 1.0], v), 0.0) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + objf, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.ResultCount()) >= 1 + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 3 atol = atol rtol = rtol + @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [0, 1, 2] atol = atol rtol = + rtol + end + MOI.delete(model, c1) + MOI.delete(model, c2) + if config.solve + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.ResultCount()) >= 1 + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 5 atol = atol rtol = rtol + @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 1, 2] atol = atol rtol = + rtol + end + return +end + +function setup_test( + ::typeof(test_SOS1_integration), + model::MOI.Utilities.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1, 2]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 2]), + ) + return +end + +""" + test_SOS2_integration(model::MOI.ModelLike, config::Config) + +Test Special Ordered Sets of type 2. +""" +function test_SOS2_integration(model::MOI.ModelLike, config::Config) + atol = config.atol + rtol = config.rtol + + @test MOI.supports_incremental_interface(model, false) #=copy_names=# + @test MOI.supports( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) + @test MOI.supports(model, MOI.ObjectiveSense()) + @test MOI.supports_constraint( + model, + MOI.VectorOfVariables, + MOI.SOS1{Float64}, + ) + @test MOI.supports_constraint( + model, + MOI.VectorOfVariables, + MOI.SOS2{Float64}, + ) + @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) + @test MOI.supports_constraint( + model, + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + ) + MOI.empty!(model) + @test MOI.is_empty(model) + v = MOI.add_variables(model, 10) + @test MOI.get(model, MOI.NumberOfVariables()) == 10 + bin_constraints = [] + for i in 1:8 + vc = MOI.add_constraint( model, - MOI.ScalarAffineFunction{Float64}, - MOI.EqualTo{Float64}, + MOI.SingleVariable(v[i]), + MOI.Interval(0.0, 2.0), ) - MOI.empty!(model) - @test MOI.is_empty(model) - v = MOI.add_variables(model, 10) - @test MOI.get(model, MOI.NumberOfVariables()) == 10 - bin_constraints = [] - for i in 1:8 - vc = MOI.add_constraint( - model, - MOI.SingleVariable(v[i]), - MOI.Interval(0.0, 2.0), - ) - @test vc.value == v[i].value - push!( - bin_constraints, - MOI.add_constraint( - model, - MOI.SingleVariable(v[i]), - MOI.ZeroOne(), - ), - ) - @test bin_constraints[i].value == v[i].value - end - MOI.add_constraint( - model, - MOI.ScalarAffineFunction( - MOI.ScalarAffineTerm.([1.0, 2.0, 3.0, -1.0], v[[1, 2, 3, 9]]), - 0.0, - ), - MOI.EqualTo(0.0), + @test vc.value == v[i].value + push!( + bin_constraints, + MOI.add_constraint(model, MOI.SingleVariable(v[i]), MOI.ZeroOne()), ) - MOI.add_constraint( - model, - MOI.ScalarAffineFunction( - MOI.ScalarAffineTerm.( - [5.0, 4.0, 7.0, 2.0, 1.0, -1.0], - v[[4, 5, 6, 7, 8, 10]], - ), - 0.0, + @test bin_constraints[i].value == v[i].value + end + MOI.add_constraint( + model, + MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([1.0, 2.0, 3.0, -1.0], v[[1, 2, 3, 9]]), + 0.0, + ), + MOI.EqualTo(0.0), + ) + MOI.add_constraint( + model, + MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.( + [5.0, 4.0, 7.0, 2.0, 1.0, -1.0], + v[[4, 5, 6, 7, 8, 10]], ), - MOI.EqualTo(0.0), - ) - MOI.add_constraint( - model, - MOI.VectorOfVariables(v[[1, 2, 3]]), - MOI.SOS1([1.0, 2.0, 3.0]), - ) - vv = MOI.VectorOfVariables(v[[4, 5, 6, 7, 8]]) - sos2 = MOI.SOS2([5.0, 4.0, 7.0, 2.0, 1.0]) - c = MOI.add_constraint(model, vv, sos2) - #= - To allow for permutations in the sets and variable vectors - we're going to sort according to the weights - =# - cs_sos = MOI.get(model, MOI.ConstraintSet(), c) - cf_sos = MOI.get(model, MOI.ConstraintFunction(), c) - p = sortperm(cs_sos.weights) - @test cs_sos.weights[p] ≈ [1.0, 2.0, 4.0, 5.0, 7.0] atol = atol rtol = - rtol - @test cf_sos.variables[p] == v[[8, 7, 5, 4, 6]] - objf = MOI.ScalarAffineFunction( - MOI.ScalarAffineTerm.([1.0, 1.0], [v[9], v[10]]), 0.0, - ) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - objf, - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - if config.solve - @test MOI.get(model, MOI.TerminationStatus()) == - MOI.OPTIMIZE_NOT_CALLED - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == - config.optimal_status - @test MOI.get(model, MOI.ResultCount()) >= 1 - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 15.0 atol = atol rtol = - rtol - @test MOI.get(model, MOI.VariablePrimal(), v) ≈ - [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.0, 12.0] atol = - atol rtol = rtol - end - for cref in bin_constraints - MOI.delete(model, cref) - end - if config.solve - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == - config.optimal_status - @test MOI.get(model, MOI.ResultCount()) >= 1 - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 30.0 atol = atol rtol = - rtol - @test MOI.get(model, MOI.VariablePrimal(), v) ≈ - [0.0, 0.0, 2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 6.0, 24.0] atol = - atol rtol = rtol - end + ), + MOI.EqualTo(0.0), + ) + MOI.add_constraint( + model, + MOI.VectorOfVariables(v[[1, 2, 3]]), + MOI.SOS1([1.0, 2.0, 3.0]), + ) + vv = MOI.VectorOfVariables(v[[4, 5, 6, 7, 8]]) + sos2 = MOI.SOS2([5.0, 4.0, 7.0, 2.0, 1.0]) + c = MOI.add_constraint(model, vv, sos2) + #= + To allow for permutations in the sets and variable vectors + we're going to sort according to the weights + =# + cs_sos = MOI.get(model, MOI.ConstraintSet(), c) + cf_sos = MOI.get(model, MOI.ConstraintFunction(), c) + p = sortperm(cs_sos.weights) + @test cs_sos.weights[p] ≈ [1.0, 2.0, 4.0, 5.0, 7.0] atol = atol rtol = rtol + @test cf_sos.variables[p] == v[[8, 7, 5, 4, 6]] + objf = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([1.0, 1.0], [v[9], v[10]]), + 0.0, + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + objf, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.ResultCount()) >= 1 + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 15.0 atol = atol rtol = + rtol + @test MOI.get(model, MOI.VariablePrimal(), v) ≈ + [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.0, 12.0] atol = atol rtol = + rtol + end + for cref in bin_constraints + MOI.delete(model, cref) + end + if config.solve + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.ResultCount()) >= 1 + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 30.0 atol = atol rtol = + rtol + @test MOI.get(model, MOI.VariablePrimal(), v) ≈ + [0.0, 0.0, 2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 6.0, 24.0] atol = atol rtol = + rtol end + return +end + +function setup_test( + ::typeof(test_SOS2_integration), + model::MOI.Utilities.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.0, 12.0], + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + [0.0, 0.0, 2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 6.0, 24.0], + ), + ) + return end -# CPLEX #76 -function int3test(model::MOI.ModelLike, config::Config) +""" + test_integer_solve_twice(model::MOI.ModelLike, config::Config) + +Test solving a model twice on the integer knapsack problem. +The problem is: +``` +max z - 0.5 ( b1 + b2 + b3) / 40 +s.t. 0 <= z - 0.5 eᵀ b / 40 <= 0.999 + b1, b2, ... b10 ∈ {0, 1} + z in {0, 1, 2, ..., 100} +``` +""" +function test_integer_solve_twice(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # integer knapsack problem - # max z - 0.5 ( b1 + b2 + b3) / 40 - # s.t. 0 <= z - 0.5 eᵀ b / 40 <= 0.999 - # b1, b2, ... b10 ∈ {0, 1} - # z in {0, 1, 2, ..., 100} MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -443,17 +482,36 @@ function int3test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 atol = atol rtol = rtol end + return +end + +function setup_test( + ::typeof(test_integer_solve_twice), + model::MOI.Utilities.MockOptimizer, + ::Config, +) + # FIXME [1, 0...] is not the correct optimal solution but it passes the test + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1.0; zeros(10)]), + ) + return end -# Mixed-integer linear problems +""" + test_integer_knapsack(model::MOI.ModelLike, config::Config) -function knapsacktest(model::MOI.ModelLike, config::Config) +Test the integer knapsack problem +``` +max 5a + 3b + 2c + 7d + 4e +st 2a + 8b + 4c + 2d + 5e <= 10 + a, b, c, d, e ∈ binary +``` +""" +function test_integer_knapsack(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # integer knapsack problem - # max 5a + 3b + 2c + 7d + 4e - # st 2a + 8b + 4c + 2d + 5e <= 10 - # a,b,c,d,e ∈ binary MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -519,17 +577,37 @@ function knapsacktest(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 0, 0, 1, 1] atol = atol rtol = rtol end + return +end + +function setup_test( + ::typeof(test_integer_knapsack), + model::MOI.Utilities.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1, 0, 0, 1, 1]), + ) + return end -function indicator1_test(model::MOI.ModelLike, config::Config) +""" + test_Indicator_integration(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +max 2x1 + 3x2 +s.t. x1 + x2 <= 10 + z1 ==> x2 <= 8 + z2 ==> x2 + x1/5 <= 9 + z1 + z2 >= 1 +``` +""" +function test_Indicator_integration(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # linear problem with indicator constraint - # max 2x1 + 3x2 - # s.t. x1 + x2 <= 10 - # z1 ==> x2 <= 8 - # z2 ==> x2 + x1/5 <= 9 - # z1 + z2 >= 1 MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports( @@ -621,20 +699,39 @@ function indicator1_test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol = atol rtol = rtol end + return end -function indicator2_test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_Indicator_integration), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1.25, 8.75, 0.0, 1.0]), + ) + return +end + +""" + test_Indicator_ON_ONE(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +max 2x1 + 3x2 - 30 z2 +s.t. x1 + x2 <= 10 + z1 ==> x2 <= 8 + z2 ==> x2 + x1/5 <= 9 + z1 + z2 >= 1 +``` +""" +function test_Indicator_ON_ONE(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # linear problem with indicator constraint - # max 2x1 + 3x2 - 30 z2 - # s.t. x1 + x2 <= 10 - # z1 ==> x2 <= 8 - # z2 ==> x2 + x1/5 <= 9 - # z1 + z2 >= 1 MOI.empty!(model) @test MOI.is_empty(model) - # This is the same model as indicator_test1, except that the penalty on z2 forces z1 to be 1. x1 = MOI.add_variable(model) x2 = MOI.add_variable(model) z1 = MOI.add_variable(model) @@ -704,18 +801,37 @@ function indicator2_test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 0.0 atol = atol rtol = rtol end + return end -function indicator3_test(model::MOI.ModelLike, config::Config) +function setup_test( + ::typeof(test_Indicator_ON_ONE), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [2.0, 8.0, 1.0, 0.0]), + ) + return +end + +""" + test_Indicator_ON_ZERO(model::MOI.ModelLike, config::Config) + +Test the problem: +``` +max 2x1 + 3x2 +s.t. x1 + x2 <= 10 + z1 == 0 ==> x2 <= 8 + z2 == 1 ==> x2 + x1/5 <= 9 + (1-z1) + z2 >= 1 <=> z2 - z1 >= 0 +``` +""" +function test_Indicator_ON_ZERO(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # linear problem with indicator constraint - # similar to indicator1_test with reversed z1 - # max 2x1 + 3x2 - # s.t. x1 + x2 <= 10 - # z1 == 0 ==> x2 <= 8 - # z2 == 1 ==> x2 + x1/5 <= 9 - # (1-z1) + z2 >= 1 <=> z2 - z1 >= 0 MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports( @@ -809,18 +925,38 @@ function indicator3_test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol = atol rtol = rtol end + return +end + +function setup_test( + ::typeof(test_Indicator_ON_ZERO), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1.25, 8.75, 1.0, 1.0]), + ) + return end -function indicator4_test(model::MOI.ModelLike, config::Config) +""" + test_Indicator_constant_term(model::MOI.ModelLike, config::Config) + +Test Indicator constraints with a constant term on the left-hand side. The +problem is: +``` +max 2x1 + 3x2 +s.t. x1 + x2 <= 10 + z1 ==> x2 - 1 <= 7 + z2 ==> x2 + x1/5 + 1 <= 10 + z1 + z2 >= 1 +``` +""" +function test_Indicator_constant_term(model::MOI.ModelLike, config::Config) atol = config.atol rtol = config.rtol - # equivalent to indicator1_test with left-hand-side partially in LHS constant - # linear problem with indicator constraint and - # max 2x1 + 3x2 - # s.t. x1 + x2 <= 10 - # z1 ==> x2 - 1 <= 7 - # z2 ==> x2 + x1/5 + 1 <= 10 - # z1 + z2 >= 1 MOI.empty!(model) @test MOI.is_empty(model) @test MOI.supports( @@ -912,9 +1048,27 @@ function indicator4_test(model::MOI.ModelLike, config::Config) @test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol = atol rtol = rtol end + return +end + +function setup_test( + ::typeof(test_Indicator_constant_term), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> + MOIU.mock_optimize!(mock, [1.25, 8.75, 0.0, 1.0]), + ) + return end -function _semitest(model::MOI.ModelLike, config::Config{T}, int::Bool) where {T} +function _test_SemiXXX_integration( + model::MOI.ModelLike, + config::Config{T}, + use_semiinteger::Bool, +) where {T} atol = config.atol rtol = config.rtol @test MOI.supports_incremental_interface(model, false) #=copy_names=# @@ -923,17 +1077,17 @@ function _semitest(model::MOI.ModelLike, config::Config{T}, int::Bool) where {T} MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), ) @test MOI.supports(model, MOI.ObjectiveSense()) - if !int + if use_semiinteger @test MOI.supports_constraint( model, MOI.SingleVariable, - MOI.Semicontinuous{T}, + MOI.Semiinteger{T}, ) else @test MOI.supports_constraint( model, MOI.SingleVariable, - MOI.Semiinteger{T}, + MOI.Semicontinuous{T}, ) end # 2 variables @@ -949,7 +1103,7 @@ function _semitest(model::MOI.ModelLike, config::Config{T}, int::Bool) where {T} @test MOI.is_empty(model) v = MOI.add_variables(model, 2) @test MOI.get(model, MOI.NumberOfVariables()) == 2 - if !int + if !use_semiinteger vc1 = MOI.add_constraint( model, MOI.SingleVariable(v[1]), @@ -1057,7 +1211,7 @@ function _semitest(model::MOI.ModelLike, config::Config{T}, int::Bool) where {T} @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status @test MOI.get(model, MOI.ResultCount()) >= 1 @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - if !int + if !use_semiinteger @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2.5 atol = atol rtol = rtol @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [2.5, 2.5] atol = @@ -1095,26 +1249,91 @@ function _semitest(model::MOI.ModelLike, config::Config{T}, int::Bool) where {T} MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE_OR_UNBOUNDED end + return end -function semiconttest(model::MOI.ModelLike, config::Config) - return _semitest(model, config, false) -end -function semiinttest(model::MOI.ModelLike, config::Config) - return _semitest(model, config, true) +""" + test_Semicontinuous_integration(model::MOI.ModelLike, config::Config) + +Run an integration test on Semicontinuous constraints. +""" +function test_Semicontinuous_integration(model::MOI.ModelLike, config::Config) + _test_SemiXXX_integration(model, config, false) + return end -const intlineartests = Dict( - "knapsack" => knapsacktest, - "int1" => int1test, - "int2" => int2test, - "int3" => int3test, - "indicator1" => indicator1_test, - "indicator2" => indicator2_test, - "indicator3" => indicator3_test, - "indicator4" => indicator4_test, - "semiconttest" => semiconttest, - "semiinttest" => semiinttest, +function setup_test( + ::typeof(test_Semicontinuous_integration), + model::MOIU.MockOptimizer, + ::Config, ) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 0.0) + MOIU.mock_optimize!(mock, [0.0, 0.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.0) + MOIU.mock_optimize!(mock, [2.0, 1.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.0) + MOIU.mock_optimize!(mock, [2.0, 2.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.5) + MOIU.mock_optimize!(mock, [2.5, 2.5]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 3.0) + MOIU.mock_optimize!(mock, [3.0, 3.0]) + end, + (mock::MOIU.MockOptimizer) -> + MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), + ) + return +end -@moitestset intlinear +""" + test_Semiinteger_integration(model::MOI.ModelLike, config::Config) + +Run an integration test on Semiinteger constraints. +""" +function test_Semiinteger_integration(model::MOI.ModelLike, config::Config) + _test_SemiXXX_integration(model, config, true) + return +end + +function setup_test( + ::typeof(test_Semiinteger_integration), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 0.0) + MOIU.mock_optimize!(mock, [0.0, 0.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.0) + MOIU.mock_optimize!(mock, [2.0, 1.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 2.0) + MOIU.mock_optimize!(mock, [2.0, 2.0]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 3.0) + MOIU.mock_optimize!(mock, [3.0, 2.5]) + end, + (mock::MOIU.MockOptimizer) -> begin + MOI.set(mock, MOI.ObjectiveBound(), 3.0) + MOIU.mock_optimize!(mock, [3.0, 3.0]) + end, + (mock::MOIU.MockOptimizer) -> + MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), + ) + return +end diff --git a/src/Test/nlp.jl b/src/Test/nlp.jl index c78e39de38..670a697f3e 100644 --- a/src/Test/nlp.jl +++ b/src/Test/nlp.jl @@ -1,5 +1,19 @@ -const VI = MOI.VariableIndex +""" + HS071( + enable_hessian::Bool, + enable_hessian_vector_product::Bool = false, + ) +An AbstractNLPEvaluator for the problem: +``` +min x1 * x4 * (x1 + x2 + x3) + x3 +st x1 * x2 * x3 * x4 >= 25 + x1^2 + x2^2 + x3^2 + x4^2 = 40 + 1 <= x1, x2, x3, x4 <= 5 +``` +Start at (1,5,5,1) +End at (1.000..., 4.743..., 3.821..., 1.379...) +""" struct HS071 <: MOI.AbstractNLPEvaluator enable_hessian::Bool enable_hessian_vector_product::Bool @@ -11,14 +25,6 @@ struct HS071 <: MOI.AbstractNLPEvaluator end end -# hs071 -# min x1 * x4 * (x1 + x2 + x3) + x3 -# st x1 * x2 * x3 * x4 >= 25 -# x1^2 + x2^2 + x3^2 + x4^2 = 40 -# 1 <= x1, x2, x3, x4 <= 5 -# Start at (1,5,5,1) -# End at (1.000..., 4.743..., 3.821..., 1.379...) - function MOI.initialize(d::HS071, requested_features::Vector{Symbol}) for feat in requested_features if !(feat in MOI.features_available(d)) @@ -39,35 +45,47 @@ function MOI.features_available(d::HS071) return features end -function MOI.objective_expr(d::HS071) +function MOI.objective_expr(::HS071) return :( - x[$(VI(1))] * x[$(VI(4))] * (x[$(VI(1))] + x[$(VI(2))] + x[$(VI(3))]) + - x[$(VI(3))] + x[$(MOI.VariableIndex(1))] * + x[$(MOI.VariableIndex(4))] * + ( + x[$(MOI.VariableIndex(1))] + + x[$(MOI.VariableIndex(2))] + + x[$(MOI.VariableIndex(3))] + ) + x[$(MOI.VariableIndex(3))] ) end -function MOI.constraint_expr(d::HS071, i::Int) +function MOI.constraint_expr(::HS071, i::Int) if i == 1 - return :(x[$(VI(1))] * x[$(VI(2))] * x[$(VI(3))] * x[$(VI(4))] >= 25.0) + return :( + x[$(MOI.VariableIndex(1))] * + x[$(MOI.VariableIndex(2))] * + x[$(MOI.VariableIndex(3))] * + x[$(MOI.VariableIndex(4))] >= 25.0 + ) elseif i == 2 return :( - x[$(VI(1))]^2 + x[$(VI(2))]^2 + x[$(VI(3))]^2 + x[$(VI(4))]^2 == - 40.0 + x[$(MOI.VariableIndex(1))]^2 + + x[$(MOI.VariableIndex(2))]^2 + + x[$(MOI.VariableIndex(3))]^2 + + x[$(MOI.VariableIndex(4))]^2 == 40.0 ) else error("Out of bounds constraint.") end end -MOI.eval_objective(d::HS071, x) = x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3] +MOI.eval_objective(::HS071, x) = x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3] -function MOI.eval_constraint(d::HS071, g, x) +function MOI.eval_constraint(::HS071, g, x) g[1] = x[1] * x[2] * x[3] * x[4] g[2] = x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2 return end -function MOI.eval_objective_gradient(d::HS071, grad_f, x) +function MOI.eval_objective_gradient(::HS071, grad_f, x) grad_f[1] = x[1] * x[4] + x[4] * (x[1] + x[2] + x[3]) grad_f[2] = x[1] * x[4] grad_f[3] = x[1] * x[4] + 1 @@ -75,7 +93,7 @@ function MOI.eval_objective_gradient(d::HS071, grad_f, x) return end -function MOI.jacobian_structure(d::HS071) +function MOI.jacobian_structure(::HS071) return Tuple{Int64,Int64}[ (1, 1), (1, 2), @@ -87,7 +105,7 @@ function MOI.jacobian_structure(d::HS071) (2, 4), ] end -# lower triangle only + function MOI.hessian_lagrangian_structure(d::HS071) @assert d.enable_hessian return Tuple{Int64,Int64}[ @@ -104,7 +122,7 @@ function MOI.hessian_lagrangian_structure(d::HS071) ] end -function MOI.eval_constraint_jacobian(d::HS071, J, x) +function MOI.eval_constraint_jacobian(::HS071, J, x) # Constraint (row) 1 J[1] = x[2] * x[3] * x[4] # 1,1 J[2] = x[1] * x[3] * x[4] # 1,2 @@ -176,11 +194,81 @@ function MOI.eval_hessian_lagrangian_product(d::HS071, h, x, v, σ, μ) return end -function hs071test_template( - model::MOI.ModelLike, - config::Config, - evaluator::HS071, +""" + FeasibilitySenseEvaluator(enable_hessian::Bool) + +An AbstractNLPEvaluator for the problem: +``` +Test for FEASIBILITY_SENSE. +Find x satisfying x^2 == 1. +``` +""" +struct FeasibilitySenseEvaluator <: MOI.AbstractNLPEvaluator + enable_hessian::Bool +end + +function MOI.initialize( + d::FeasibilitySenseEvaluator, + requested_features::Vector{Symbol}, ) + for feat in requested_features + if !(feat in MOI.features_available(d)) + error("Unsupported feature $feat") + # TODO: implement Jac-vec and Hess-vec products + # for solvers that need them + end + end + return +end + +function MOI.features_available(d::FeasibilitySenseEvaluator) + if d.enable_hessian + return [:Grad, :Jac, :Hess, :ExprGraph] + else + return [:Grad, :Jac, :ExprGraph] + end +end + +MOI.objective_expr(::FeasibilitySenseEvaluator) = :() + +function MOI.constraint_expr(::FeasibilitySenseEvaluator, i::Int) + @assert i == 1 + return :(x[$(MOI.VariableIndex(1))]^2 == 1) +end + +MOI.eval_objective(d::FeasibilitySenseEvaluator, x) = 0.0 + +function MOI.eval_constraint(::FeasibilitySenseEvaluator, g, x) + g[1] = x[1]^2 + return +end + +function MOI.eval_objective_gradient(::FeasibilitySenseEvaluator, grad_f, x) + grad_f[1] = 0.0 + return +end + +function MOI.jacobian_structure(::FeasibilitySenseEvaluator) + return Tuple{Int64,Int64}[(1, 1)] +end + +function MOI.hessian_lagrangian_structure(d::FeasibilitySenseEvaluator) + @assert d.enable_hessian + return Tuple{Int64,Int64}[(1, 1)] +end + +function MOI.eval_constraint_jacobian(::FeasibilitySenseEvaluator, J, x) + J[1] = 2 * x[1] + return +end + +function MOI.eval_hessian_lagrangian(d::FeasibilitySenseEvaluator, H, x, σ, μ) + @assert d.enable_hessian + H[1] = 2 * μ[1] # 1,1 + return +end + +function _test_HS071(model::MOI.ModelLike, config::Config, evaluator::HS071) atol = config.atol rtol = config.rtol @test MOI.supports(model, MOI.NLPBlock()) @@ -238,162 +326,115 @@ function hs071test_template( rtol # TODO: Duals? Maybe better to test on a convex instance. end + return end -function hs071_test(model, config) - return hs071test_template(model, config, HS071(true)) -end - -function hs071_no_hessian_test(model, config) - return hs071test_template(model, config, HS071(false)) -end - -function hs071_hessian_vector_product_test(model, config) - return hs071test_template(model, config, HS071(false, true)) -end +""" + test_nonlinear_hs071(model::MOI.ModelLike, config::Config) -# Test for FEASIBILITY_SENSE. -# Find x satisfying x^2 == 1. -struct FeasibilitySenseEvaluator <: MOI.AbstractNLPEvaluator - enable_hessian::Bool +Test the nonlinear HS071 problem. +""" +function test_nonlinear_hs071(model::MOI.ModelLike, config::Config) + _test_HS071(model, config, HS071(true)) + return end -function MOI.initialize( - d::FeasibilitySenseEvaluator, - requested_features::Vector{Symbol}, +function setup_test( + ::typeof(test_nonlinear_hs071), + model::MOIU.MockOptimizer, + config::Config, ) - for feat in requested_features - if !(feat in MOI.features_available(d)) - error("Unsupported feature $feat") - # TODO: implement Jac-vec and Hess-vec products - # for solvers that need them - end - end -end - -function MOI.features_available(d::FeasibilitySenseEvaluator) - if d.enable_hessian - return [:Grad, :Jac, :Hess, :ExprGraph] - else - return [:Grad, :Jac, :ExprGraph] - end -end - -MOI.objective_expr(d::FeasibilitySenseEvaluator) = :() - -function MOI.constraint_expr(d::FeasibilitySenseEvaluator, i::Int) - if i == 1 - return :(x[$(VI(1))]^2 == 1) - else - error("Out of bounds constraint.") - end -end - -MOI.eval_objective(d::FeasibilitySenseEvaluator, x) = 0.0 - -function MOI.eval_constraint(d::FeasibilitySenseEvaluator, g, x) - return g[1] = x[1]^2 -end - -function MOI.eval_objective_gradient(d::FeasibilitySenseEvaluator, grad_f, x) - return grad_f[1] = 0.0 -end - -function MOI.jacobian_structure(d::FeasibilitySenseEvaluator) - return Tuple{Int64,Int64}[(1, 1)] -end - -function MOI.hessian_lagrangian_structure(d::FeasibilitySenseEvaluator) - @assert d.enable_hessian - return Tuple{Int64,Int64}[(1, 1)] + MOI.Utilities.set_mock_optimize!( + model, + (mock) -> begin + MOI.Utilities.mock_optimize!( + mock, + config.optimal_status, + [ + 1.0, + 4.7429996418092970, + 3.8211499817883077, + 1.379408289755698, + ], + ) + MOI.set(mock, MOI.ObjectiveValue(), 17.014017145179164) + end, + ) + return end -function MOI.eval_constraint_jacobian(d::FeasibilitySenseEvaluator, J, x) - return J[1] = 2x[1] -end +""" + test_nonlinear_hs071_no_hessian(model::MOI.ModelLike, config::Config) -function MOI.eval_hessian_lagrangian(d::FeasibilitySenseEvaluator, H, x, σ, μ) - @assert d.enable_hessian - return H[1] = 2μ[1] # 1,1 +Test the nonlinear HS071 problem without hessians. +""" +function test_nonlinear_hs071_no_hessian(model, config) + _test_HS071(model, config, HS071(false)) + return end -function feasibility_sense_test_template( - model::MOI.ModelLike, +function setup_test( + ::typeof(test_nonlinear_hs071_no_hessian), + model::MOIU.MockOptimizer, config::Config, - set_has_objective::Bool, - evaluator::FeasibilitySenseEvaluator, ) - atol = config.atol - rtol = config.rtol - @test MOI.supports(model, MOI.NLPBlock()) - @test MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) - MOI.empty!(model) - @test MOI.is_empty(model) - lb = [1.0] - ub = [1.0] - block_data = MOI.NLPBlockData( - MOI.NLPBoundsPair.(lb, ub), - evaluator, - set_has_objective, - ) - x = MOI.add_variable(model) - @test MOI.get(model, MOI.NumberOfVariables()) == 1 - # Avoid starting at zero because it's a critial point. - MOI.set(model, MOI.VariablePrimalStart(), x, 1.5) - MOI.set(model, MOI.NLPBlock(), block_data) - MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) - # TODO: config.query tests - if config.solve - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status - @test MOI.get(model, MOI.ResultCount()) >= 1 - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0.0 atol = atol rtol = rtol - @test abs(MOI.get(model, MOI.VariablePrimal(), x)) ≈ 1.0 atol = atol rtol = - rtol - end -end - -function feasibility_sense_with_objective_and_hessian_test(model, config) - return feasibility_sense_test_template( + MOI.Utilities.set_mock_optimize!( model, - config, - true, - FeasibilitySenseEvaluator(true), + (mock) -> begin + MOI.Utilities.mock_optimize!( + mock, + config.optimal_status, + [ + 1.0, + 4.7429996418092970, + 3.8211499817883077, + 1.379408289755698, + ], + ) + MOI.set(mock, MOI.ObjectiveValue(), 17.014017145179164) + end, ) + return end -function feasibility_sense_with_objective_and_no_hessian_test(model, config) - return feasibility_sense_test_template( - model, - config, - true, - FeasibilitySenseEvaluator(false), +""" + test_nonlinear_hs071_hessian_vector_product( + model::MOI.ModelLike, + config::Config, ) -end -function feasibility_sense_with_no_objective_and_with_hessian_test( - model, - config, -) - return feasibility_sense_test_template( - model, - config, - false, - FeasibilitySenseEvaluator(true), - ) +Test the nonlinear HS071 problem with a hessian vector product. +""" +function test_nonlinear_hs071_hessian_vector_product(model, config) + _test_HS071(model, config, HS071(false, true)) + return end -function feasibility_sense_with_no_objective_and_no_hessian_test(model, config) - return feasibility_sense_test_template( +function setup_test( + ::typeof(test_nonlinear_hs071_hessian_vector_product), + model::MOIU.MockOptimizer, + config::Config, +) + MOI.Utilities.set_mock_optimize!( model, - config, - false, - FeasibilitySenseEvaluator(false), + (mock) -> begin + MOI.Utilities.mock_optimize!( + mock, + config.optimal_status, + [ + 1.0, + 4.7429996418092970, + 3.8211499817883077, + 1.379408289755698, + ], + ) + MOI.set(mock, MOI.ObjectiveValue(), 17.014017145179164) + end, ) + return end -function nlp_objective_and_moi_objective_test( +function test_nonlinear_objective_and_moi_objective_test( model::MOI.ModelLike, config::Config, ) @@ -426,36 +467,36 @@ function nlp_objective_and_moi_objective_test( @test MOI.get(model, MOI.ResultCount()) >= 1 @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0.0 atol = atol rtol = rtol - @test 1.0 ≤ MOI.get(model, MOI.VariablePrimal(), x) ≤ 2.0 + xv = MOI.get(model, MOI.VariablePrimal(), x) + @test abs(xv) ≈ 1.0 atol = atol rtol = rtol end end -const nlptests = Dict( - "hs071" => hs071_test, - "hs071_no_hessian" => hs071_no_hessian_test, - "hs071_hessian_vector_product_test" => - hs071_hessian_vector_product_test, - "feasibility_sense_with_objective_and_hessian" => - feasibility_sense_with_objective_and_hessian_test, - "feasibility_sense_with_objective_and_no_hessian" => - feasibility_sense_with_objective_and_no_hessian_test, - "feasibility_sense_with_no_objective_and_with_hessian" => - feasibility_sense_with_no_objective_and_with_hessian_test, - "feasibility_sense_with_no_objective_and_no_hessian" => - feasibility_sense_with_no_objective_and_no_hessian_test, - "nlp_objective_and_moi_objective" => - nlp_objective_and_moi_objective_test, +function setup_test( + ::typeof(test_nonlinear_objective_and_moi_objective_test), + model::MOIU.MockOptimizer, + config::Config, ) - -@moitestset nlp + MOI.Utilities.set_mock_optimize!( + model, + (mock) -> begin + MOI.Utilities.mock_optimize!(mock, config.optimal_status, [-1.0]), + MOI.set(mock, MOI.ObjectiveValue(), 0.0) + end, + ) + return +end """ - test_linear_mixed_complementarity(model::MOI.ModelLike, config::Config) + test_nonlinear_mixed_complementarity(model::MOI.ModelLike, config::Config) Test the solution of the linear mixed-complementarity problem: `F(x) complements x`, where `F(x) = M * x .+ q` and `0 <= x <= 10`. """ -function test_linear_mixed_complementarity(model::MOI.ModelLike, config::Config) +function test_nonlinear_mixed_complementarity( + model::MOI.ModelLike, + config::Config, +) MOI.empty!(model) x = MOI.add_variables(model, 4) MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.Interval(0.0, 10.0)) @@ -493,17 +534,30 @@ function test_linear_mixed_complementarity(model::MOI.ModelLike, config::Config) rtol = config.rtol, ) end + return end -const mixed_complementaritytests = Dict( - "test_linear_mixed_complementarity" => - test_linear_mixed_complementarity, +function setup_test( + ::typeof(test_nonlinear_mixed_complementarity), + model::MOIU.MockOptimizer, + config::Config, ) - -@moitestset mixed_complementarity + MOI.Utilities.set_mock_optimize!( + model, + (mock) -> MOI.Utilities.mock_optimize!( + mock, + config.optimal_status, + [2.8, 0.0, 0.8, 1.2], + ), + ) + return +end """ - test_qp_complementarity_constraint(model::MOI.ModelLike, config::Config) + test_nonlinear_qp_complementarity_constraint( + model::MOI.ModelLike, + config::Config, + ) Test the solution of the quadratic program with complementarity constraints: @@ -531,7 +585,7 @@ which rewrites, with auxiliary variables ``` """ -function test_qp_complementarity_constraint( +function test_nonlinear_qp_complementarity_constraint( model::MOI.ModelLike, config::Config, ) @@ -603,11 +657,126 @@ function test_qp_complementarity_constraint( rtol = config.rtol, ) end + return end -const math_program_complementarity_constraintstests = Dict( - "test_qp_complementarity_constraint" => - test_qp_complementarity_constraint, +function setup_test( + ::typeof(test_nonlinear_qp_complementarity_constraint), + model::MOIU.MockOptimizer, + config::Config, ) + MOI.Utilities.set_mock_optimize!( + model, + (mock) -> begin + MOI.Utilities.mock_optimize!( + mock, + config.optimal_status, + [1.0, 0.0, 3.5, 0.0, 0.0, 0.0, 3.0, 6.0], + ) + MOI.set(mock, MOI.ObjectiveValue(), 17.0) + end, + ) + return +end + +""" + test_nonlinear_HS071_internal(::MOI.ModelLike, ::Config) + +A test for the correctness of the HS071 evaluator. -@moitestset math_program_complementarity_constraints +This is mainly for the internal purpose of checking their correctness as +written. External solvers can exclude this test without consequence. +""" +function test_nonlinear_HS071_internal(::MOI.ModelLike, ::Config) + d = MOI.Test.HS071(true, true) + @test MOI.objective_expr(d) == :( + x[$(MOI.VariableIndex(1))] * + x[$(MOI.VariableIndex(4))] * + ( + x[$(MOI.VariableIndex(1))] + + x[$(MOI.VariableIndex(2))] + + x[$(MOI.VariableIndex(3))] + ) + x[$(MOI.VariableIndex(3))] + ) + @test MOI.constraint_expr(d, 1) == :( + x[$(MOI.VariableIndex(1))] * + x[$(MOI.VariableIndex(2))] * + x[$(MOI.VariableIndex(3))] * + x[$(MOI.VariableIndex(4))] >= 25.0 + ) + @test MOI.constraint_expr(d, 2) == :( + x[$(MOI.VariableIndex(1))]^2 + + x[$(MOI.VariableIndex(2))]^2 + + x[$(MOI.VariableIndex(3))]^2 + + x[$(MOI.VariableIndex(4))]^2 == 40.0 + ) + @test_throws ErrorException MOI.constraint_expr(d, 3) + MOI.initialize(d, [:Grad, :Jac, :ExprGraph, :Hess, :HessVec]) + @test :HessVec in MOI.features_available(d) + x = ones(4) + # f(x) + @test MOI.eval_objective(d, x) == 4.0 + # g(x) + g = zeros(2) + MOI.eval_constraint(d, g, x) + @test g == [1.0, 4.0] + # f'(x) + ∇f = fill(NaN, length(x)) + MOI.eval_objective_gradient(d, ∇f, x) + @test ∇f == [4.0, 1.0, 2.0, 3.0] + # Jacobian + Js = MOI.jacobian_structure(d) + J = fill(NaN, length(Js)) + MOI.eval_constraint_jacobian(d, J, x) + @test J == [1, 1, 1, 1, 2, 2, 2, 2] + # Hessian-lagrangian + Hs = MOI.hessian_lagrangian_structure(d) + H = fill(NaN, length(Hs)) + MOI.eval_hessian_lagrangian(d, H, x, 1.0, [1.0, 1.0]) + @test H == [4, 2, 2, 2, 1, 2, 5, 2, 2, 2] + # Hessian-lagrangian-product + Hv = fill(NaN, length(x)) + v = [1.0, 1.1, 1.2, 1.3] + MOI.eval_hessian_lagrangian_product(d, Hv, x, v, 1.0, [1.0, 1.0]) + @test Hv == [15.1, 8.0, 8.1, 12.2] + return +end + +""" + test_nonlinear_Feasibility_internal(::MOI.ModelLike, ::Config) + +A test for the correctness of the FeasibilitySenseEvaluator evaluator. + +This is mainly for the internal purpose of checking their correctness as +written. External solvers can exclude this test without consequence. +""" +function test_nonlinear_Feasibility_internal(::MOI.ModelLike, ::Config) + d = MOI.Test.FeasibilitySenseEvaluator(true) + @test MOI.objective_expr(d) == :() + @test MOI.constraint_expr(d, 1) == :(x[$(MOI.VariableIndex(1))]^2 == 1.0) + @test_throws AssertionError MOI.constraint_expr(d, 2) + MOI.initialize(d, [:Grad, :Jac, :ExprGraph, :Hess]) + @test :Hess in MOI.features_available(d) + x = [1.5] + # f(x) + @test MOI.eval_objective(d, x) == 0.0 + # g(x) + g = zeros(1) + MOI.eval_constraint(d, g, x) + @test g == [1.5^2] + # f'(x) + ∇f = fill(NaN, length(x)) + MOI.eval_objective_gradient(d, ∇f, x) + @test ∇f == [0.0] + # Jacobian + Js = MOI.jacobian_structure(d) + J = fill(NaN, length(Js)) + MOI.eval_constraint_jacobian(d, J, x) + @test J == [3.0] + # Hessian-lagrangian + Hs = MOI.hessian_lagrangian_structure(d) + H = fill(NaN, length(Hs)) + MOI.eval_hessian_lagrangian(d, H, x, 1.0, [1.1]) + @test H == [2.2] + return +end diff --git a/test/Bridges/Constraint/det.jl b/test/Bridges/Constraint/det.jl index 0f5c1c690d..8efc9446f6 100644 --- a/test/Bridges/Constraint/det.jl +++ b/test/Bridges/Constraint/det.jl @@ -33,8 +33,14 @@ config = MOIT.Config() ) => [psd_dual], ) - MOIT.logdett1vtest(bridged_mock, config) - MOIT.logdett1ftest(bridged_mock, config) + MOIT.test_conic_LogDetConeTriangle_VectorOfVariables( + bridged_mock, + config, + ) + MOIT.test_conic_LogDetConeTriangle_VectorAffineFunction( + bridged_mock, + config, + ) # set primal/dual start is not yet implemented for LogDet bridge ci = first( @@ -106,7 +112,7 @@ config = MOIT.Config() ) => [psd_dual], ) - MOIT.logdett2test(bridged_mock, config) + MOIT.test_conic_LogDetConeTriangle(bridged_mock, config) # set primal/dual start is not yet implemented for LogDet bridge ci = first( @@ -155,8 +161,14 @@ end ) => [psd_dual], ) - MOIT.rootdett1vtest(bridged_mock, config) - MOIT.rootdett1ftest(bridged_mock, config) + MOIT.test_conic_RootDetConeTriangle_VectorOfVariables( + bridged_mock, + config, + ) + MOIT.test_conic_RootDetConeTriangle_VectorAffineFunction( + bridged_mock, + config, + ) # set primal/dual start is not yet implemented for RootDet bridge ci = first( @@ -227,7 +239,7 @@ end ) => [psd_dual], ) - MOIT.rootdett2test(bridged_mock, config) + MOIT.test_conic_RootDetConeTriangle(bridged_mock, config) # set primal/dual start is not yet implemented for RootDet bridge ci = first( diff --git a/test/Bridges/Constraint/flip_sign.jl b/test/Bridges/Constraint/flip_sign.jl index baa137ea61..8bd1ccdba4 100644 --- a/test/Bridges/Constraint/flip_sign.jl +++ b/test/Bridges/Constraint/flip_sign.jl @@ -33,7 +33,10 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0]), ) - MOIT.linear6test(bridged_mock, config) + MOIT.test_linear_modify_GreaterThan_and_LessThan_constraints( + bridged_mock, + config, + ) ci = first( MOI.get( @@ -96,7 +99,7 @@ end [1.0], ), ) - MOIT.solve_set_scalaraffine_lessthan(bridged_mock, config) + MOIT.test_modification_set_scalaraffine_lessthan(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -117,7 +120,7 @@ end [0.5], ), ) - MOIT.solve_coef_scalaraffine_lessthan(bridged_mock, config) + MOIT.test_modification_coef_scalaraffine_lessthan(bridged_mock, config) ci = first( MOI.get( @@ -168,7 +171,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0]), ) - MOIT.linear7test(bridged_mock, config) + MOIT.test_linear_VectorAffineFunction(bridged_mock, config) ci = first( MOI.get( @@ -195,7 +198,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3.0, -1.0]], ) - MOIT.lin1vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables(bridged_mock, config) ci = first( MOI.get( bridged_mock, @@ -250,7 +253,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0]), ) - MOIT.linear7test(bridged_mock, config) + MOIT.test_linear_VectorAffineFunction(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -265,7 +268,7 @@ end (MOI.FEASIBLE_POINT, [1.0, 0.75]), ), ) - MOIT.solve_const_vectoraffine_nonpos(bridged_mock, config) + MOIT.test_modification_const_vectoraffine_nonpos(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -277,7 +280,7 @@ end (MOI.FEASIBLE_POINT, [0.25]), ), ) - MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config) + MOIT.test_modification_multirow_vectoraffine_nonpos(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/functionize.jl b/test/Bridges/Constraint/functionize.jl index 13d7f24796..e96ac0a64a 100644 --- a/test/Bridges/Constraint/functionize.jl +++ b/test/Bridges/Constraint/functionize.jl @@ -70,7 +70,7 @@ config_with_basis = MOIT.Config(basis = true) var_basis = [MOI.BASIC, MOI.NONBASIC_AT_LOWER], ), ) - MOIT.linear2test(bridged_mock, config_with_basis) + MOIT.test_linear_integration_2(bridged_mock, config_with_basis) cis = MOI.get( bridged_mock, @@ -166,7 +166,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]], ) - MOIT.lin1vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables(bridged_mock, config) ci = first( MOI.get( bridged_mock, diff --git a/test/Bridges/Constraint/interval.jl b/test/Bridges/Constraint/interval.jl index b8f35ec140..3753f3166f 100644 --- a/test/Bridges/Constraint/interval.jl +++ b/test/Bridges/Constraint/interval.jl @@ -85,7 +85,7 @@ end var_basis = [MOI.BASIC, MOI.BASIC], ), ) - MOIT.linear10test(bridged_mock, config_with_basis) + MOIT.test_linear_integration_Interval(bridged_mock, config_with_basis) MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -106,7 +106,7 @@ end [0], ), ) - MOIT.linear10btest(bridged_mock, config_with_basis) + MOIT.test_linear_Interval_inactive(bridged_mock, config_with_basis) ci = first( MOI.get( @@ -176,7 +176,7 @@ end (MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => zeros(T, 1), ), ) - MOIT.linear13test(bridged_mock, MOIT.Config{T}()) + MOIT.test_linear_FEASIBILITY_SENSE(bridged_mock, MOIT.Config{T}()) ci = first( MOI.get( @@ -209,7 +209,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-3, -1]], ) - MOIT.lin1vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/ltgt_to_interval.jl b/test/Bridges/Constraint/ltgt_to_interval.jl index 35a579bd4f..991e4e9de4 100644 --- a/test/Bridges/Constraint/ltgt_to_interval.jl +++ b/test/Bridges/Constraint/ltgt_to_interval.jl @@ -34,7 +34,10 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100.0, -100.0]), ) - MOIT.linear6test(bridged_mock, config) + MOIT.test_linear_modify_GreaterThan_and_LessThan_constraints( + bridged_mock, + config, + ) ci = first( MOI.get( @@ -97,7 +100,7 @@ end [-1.0], ), ) - MOIT.solve_set_scalaraffine_lessthan(bridged_mock, config) + MOIT.test_modification_set_scalaraffine_lessthan(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -118,7 +121,7 @@ end [-0.5], ), ) - MOIT.solve_coef_scalaraffine_lessthan(bridged_mock, config) + MOIT.test_modification_coef_scalaraffine_lessthan(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/norm_spec_nuc_to_psd.jl b/test/Bridges/Constraint/norm_spec_nuc_to_psd.jl index 09c5860955..4782a29b57 100644 --- a/test/Bridges/Constraint/norm_spec_nuc_to_psd.jl +++ b/test/Bridges/Constraint/norm_spec_nuc_to_psd.jl @@ -56,7 +56,7 @@ config = MOIT.Config() ) => [psd_dual], ) - MOIT.normspec1test(bridged_mock, config) + MOIT.test_conic_NormSpectralCone(bridged_mock, config) var_names = ["t"] MOI.set( @@ -203,7 +203,7 @@ end ) => [psd_dual], ) - MOIT.normnuc1test(bridged_mock, config) + MOIT.test_conic_NormNuclearCone(bridged_mock, config) greater = MOI.get( mock, diff --git a/test/Bridges/Constraint/norm_to_lp.jl b/test/Bridges/Constraint/norm_to_lp.jl index d8e56b3cc9..919b4b29e8 100644 --- a/test/Bridges/Constraint/norm_to_lp.jl +++ b/test/Bridges/Constraint/norm_to_lp.jl @@ -37,8 +37,11 @@ config = MOIT.Config() [[-1], [-1]], ) - MOIT.norminf1vtest(bridged_mock, config) - MOIT.norminf1ftest(bridged_mock, config) + MOIT.test_conic_NormInfinityCone_VectorOfVariables(bridged_mock, config) + MOIT.test_conic_NormInfinityCone_VectorAffineFunction( + bridged_mock, + config, + ) var_names = ["x", "y", "z"] @@ -171,7 +174,7 @@ config = MOIT.Config() ) end - @testset "norminf3test" begin + @testset "test_conic_NormInfinityCone_3" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -179,7 +182,7 @@ config = MOIT.Config() (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [vcat(fill(inv(3), 3), zeros(3)), fill(inv(3), 3)], ) - MOIT.norminf3test(bridged_mock, config) + MOIT.test_conic_NormInfinityCone_3(bridged_mock, config) var_names = ["x", "y1", "y2", "y3"] @@ -327,8 +330,8 @@ end [[-1], [0]], ) - MOIT.normone1vtest(bridged_mock, config) - MOIT.normone1ftest(bridged_mock, config) + MOIT.test_conic_NormOneCone_VectorOfVariables(bridged_mock, config) + MOIT.test_conic_NormOneCone_VectorAffineFunction(bridged_mock, config) var_names = ["x", "y", "z"] MOI.set( @@ -452,7 +455,7 @@ end ) end - @testset "normone3test" begin + @testset "test_conic_NormOneCone" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -460,7 +463,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [vcat(ones(4), zeros(3)), ones(3)], ) - MOIT.normone3test(bridged_mock, config) + MOIT.test_conic_NormOneCone(bridged_mock, config) var_names = ["x", "y1", "y2", "y3"] diff --git a/test/Bridges/Constraint/quad_to_soc.jl b/test/Bridges/Constraint/quad_to_soc.jl index fe72710782..13b7827f94 100644 --- a/test/Bridges/Constraint/quad_to_soc.jl +++ b/test/Bridges/Constraint/quad_to_soc.jl @@ -52,7 +52,8 @@ config = MOIT.Config() (MOI.FEASIBLE_POINT, [0.5, (√13 - 1) / 4]), ), ) - MOIT.solve_qcp_edge_cases(bridged_mock, config) + MOIT.test_qcp_duplicate_diagonal(bridged_mock, config) + MOIT.test_qcp_duplicate_off_diagonal(bridged_mock, config) ci = first( MOI.get( mock, @@ -92,7 +93,7 @@ config = MOIT.Config() ) => [[0.25, 1.0, -1 / √2]], ), ) - MOIT.qcp1test(bridged_mock, config) + MOIT.test_quadratic_constraint_integration(bridged_mock, config) MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -104,8 +105,8 @@ config = MOIT.Config() ) => [[1 / √2, 1 / (2 * √2), -1 / √2]], ), ) - MOIT.qcp2test(bridged_mock, config) - MOIT.qcp3test(bridged_mock, config) + MOIT.test_quadratic_constraint_basic(bridged_mock, config) + MOIT.test_quadratic_constraint_minimize(bridged_mock, config) @testset "Bridge deletion" begin ci = first( MOI.get( @@ -138,7 +139,7 @@ config = MOIT.Config() ) => [[1.0, 1 / 3, -1 / √2, -1 / √6]], ), ) - MOIT.qcp4test(bridged_mock, config) - MOIT.qcp5test(bridged_mock, config) + MOIT.test_quadratic_constraint_LessThan(bridged_mock, config) + MOIT.test_quadratic_constraint_GreaterThan(bridged_mock, config) end end diff --git a/test/Bridges/Constraint/relentr_to_exp.jl b/test/Bridges/Constraint/relentr_to_exp.jl index fe75bf1c7d..aa71e0d547 100644 --- a/test/Bridges/Constraint/relentr_to_exp.jl +++ b/test/Bridges/Constraint/relentr_to_exp.jl @@ -40,7 +40,7 @@ config = MOIT.Config() exps_duals, ) - MOIT.relentr1test(bridged_mock, config) + MOIT.test_conic_RelativeEntropyCone(bridged_mock, config) var_names = ["u"] MOI.set( diff --git a/test/Bridges/Constraint/rsoc.jl b/test/Bridges/Constraint/rsoc.jl index f06b2429a2..fa99e34b2b 100644 --- a/test/Bridges/Constraint/rsoc.jl +++ b/test/Bridges/Constraint/rsoc.jl @@ -34,7 +34,10 @@ config = MOIT.Config() (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3 / 2, 1 / 2, -1.0, -1.0]], ) - MOIT.rotatedsoc1vtest(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorOfVariables( + bridged_mock, + config, + ) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -42,7 +45,10 @@ config = MOIT.Config() (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3 / 2, 1 / 2, -1.0, -1.0]], ) - MOIT.rotatedsoc1ftest(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorAffineFunction( + bridged_mock, + config, + ) ci = first( MOI.get( bridged_mock, @@ -93,7 +99,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1 / √2, 1 + 1 / √2, -1]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], ) - MOIT.soc1vtest(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorOfVariables(bridged_mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -101,7 +107,7 @@ end (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1 / √2, 1 + 1 / √2, -1]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], ) - MOIT.soc1ftest(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorAffineFunction(bridged_mock, config) ci = first( MOI.get( bridged_mock, diff --git a/test/Bridges/Constraint/scalarize.jl b/test/Bridges/Constraint/scalarize.jl index e0c12d0742..85c85b94da 100644 --- a/test/Bridges/Constraint/scalarize.jl +++ b/test/Bridges/Constraint/scalarize.jl @@ -35,7 +35,7 @@ config = MOIT.Config() (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1], ) - MOIT.lin1vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables(bridged_mock, config) ci = first( MOI.get( bridged_mock, @@ -100,7 +100,7 @@ config = MOIT.Config() (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1], ) - MOIT.lin1ftest(bridged_mock, config) + MOIT.test_conic_linear_VectorAffineFunction(bridged_mock, config) ci = first( MOI.get( @@ -168,7 +168,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.75]), ) - MOIT.solve_func_vectoraffine_nonneg(bridged_mock, config) + MOIT.test_modification_func_vectoraffine_nonneg(bridged_mock, config) # VectorOfVariables-in-Nonnegatives # VectorOfVariables-in-Nonpositives @@ -181,7 +181,7 @@ config = MOIT.Config() (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4], ) - MOIT.lin2vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables_2(bridged_mock, config) # VectorAffineFunction-in-Nonnegatives # VectorAffineFunction-in-Nonpositives @@ -197,5 +197,5 @@ config = MOIT.Config() (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4, 7], ) - MOIT.lin2ftest(bridged_mock, config) + MOIT.test_conic_linear_VectorAffineFunction_2(bridged_mock, config) end diff --git a/test/Bridges/Constraint/semi_to_binary.jl b/test/Bridges/Constraint/semi_to_binary.jl index 596bdeb391..7ac5bd1b65 100644 --- a/test/Bridges/Constraint/semi_to_binary.jl +++ b/test/Bridges/Constraint/semi_to_binary.jl @@ -63,7 +63,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), ) - MOIT.semiconttest(bridged_mock, config) + MOIT.test_Semicontinuous_integration(bridged_mock, config) ci = first( MOI.get( @@ -113,7 +113,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), ) - MOIT.semiinttest(bridged_mock, config) + MOIT.test_Semiinteger_integration(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/slack.jl b/test/Bridges/Constraint/slack.jl index e1c66b8826..4250a8229a 100644 --- a/test/Bridges/Constraint/slack.jl +++ b/test/Bridges/Constraint/slack.jl @@ -94,7 +94,10 @@ config = MOIT.Config() ], ), ) - MOIT.linear2test(bridged_mock, MOIT.Config(duals = false, basis = true)) + MOIT.test_linear_integration_2( + bridged_mock, + MOIT.Config(duals = false, basis = true), + ) c1 = MOI.get( bridged_mock, MOI.ListOfConstraintIndices{ @@ -119,7 +122,7 @@ config = MOIT.Config() (MOI.SingleVariable, MOI.LessThan{Float64}) => [0], ), ) - MOIT.linear11test(bridged_mock, MOIT.Config(duals = false)) + MOIT.test_linear_transform(bridged_mock, MOIT.Config(duals = false)) c1 = MOI.get( bridged_mock, @@ -253,7 +256,7 @@ end (MOI.VectorOfVariables, MOI.Nonpositives) => [[1.0]], ), ) - MOIT.linear7test(bridged_mock, config) + MOIT.test_linear_VectorAffineFunction(bridged_mock, config) c1 = MOI.get( bridged_mock, diff --git a/test/Bridges/Constraint/soc_to_nonconvex_quad.jl b/test/Bridges/Constraint/soc_to_nonconvex_quad.jl index e7d4093c8d..138f3f0256 100644 --- a/test/Bridges/Constraint/soc_to_nonconvex_quad.jl +++ b/test/Bridges/Constraint/soc_to_nonconvex_quad.jl @@ -44,7 +44,10 @@ config = MOIT.Config(duals = false) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 1.0, 1 / √2, 1 / √2]) - MOIT.rotatedsoc1vtest(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorOfVariables( + bridged_mock, + config, + ) ci = first( MOI.get( @@ -98,7 +101,7 @@ end mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1 / √2, 1 / √2]) - MOIT.soc1vtest(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorOfVariables(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/soc_to_psd.jl b/test/Bridges/Constraint/soc_to_psd.jl index f4ab85339f..91356128a8 100644 --- a/test/Bridges/Constraint/soc_to_psd.jl +++ b/test/Bridges/Constraint/soc_to_psd.jl @@ -36,8 +36,8 @@ config = MOIT.Config() ) => [[√2 / 2, -1 / 2, √2 / 4, -1 / 2, √2 / 4, √2 / 4]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], ) - MOIT.soc1vtest(bridged_mock, config) - MOIT.soc1ftest(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorOfVariables(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorAffineFunction(bridged_mock, config) ci = first( MOI.get( @@ -96,7 +96,10 @@ end MOI.PositiveSemidefiniteConeTriangle, ) => [[√2, -1 / 2, √2 / 8, -1 / 2, √2 / 8, √2 / 8]], ) - MOIT.rotatedsoc1vtest(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorOfVariables( + bridged_mock, + config, + ) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -106,7 +109,10 @@ end MOI.PositiveSemidefiniteConeTriangle, ) => [[√2, -1 / 2, √2 / 8, -1 / 2, √2 / 8, √2 / 8]], ) - MOIT.rotatedsoc1ftest(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorAffineFunction( + bridged_mock, + config, + ) ci = first( MOI.get( diff --git a/test/Bridges/Constraint/vectorize.jl b/test/Bridges/Constraint/vectorize.jl index 173da8a984..25d0b40de4 100644 --- a/test/Bridges/Constraint/vectorize.jl +++ b/test/Bridges/Constraint/vectorize.jl @@ -44,7 +44,7 @@ config = MOIT.Config() [[0], [1]], ), ) - MOIT.linear2test(bridged_mock, config) + MOIT.test_linear_integration_2(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -52,7 +52,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), ) - MOIT.linear4test(bridged_mock, config) + MOIT.test_linear_LessThan_and_GreaterThan(bridged_mock, config) MOIU.set_mock_optimize!( mock, @@ -61,7 +61,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2]), ) - MOIT.linear5test(mock, config) + MOIT.test_linear_integration_modification(mock, config) MOIU.set_mock_optimize!( mock, @@ -69,7 +69,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), ) - MOIT.linear6test(mock, config) + MOIT.test_linear_modify_GreaterThan_and_LessThan_constraints(mock, config) MOIU.set_mock_optimize!( mock, @@ -90,7 +90,7 @@ config = MOIT.Config() ) # linear14 has double variable bounds for the z variable mock.eval_variable_constraint_dual = false - MOIT.linear14test(bridged_mock, config) + MOIT.test_linear_integration_delete_variables(bridged_mock, config) mock.eval_variable_constraint_dual = true mock.optimize! = diff --git a/test/Bridges/Constraint/zero_one.jl b/test/Bridges/Constraint/zero_one.jl index 07698c0f91..45d99da54a 100644 --- a/test/Bridges/Constraint/zero_one.jl +++ b/test/Bridges/Constraint/zero_one.jl @@ -36,7 +36,7 @@ config = MOIT.Config() (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0, 1, 1]), ) - MOIT.knapsacktest(bridged_mock, config) + MOIT.test_integer_knapsack(bridged_mock, config) ci = first( MOI.get( @@ -65,14 +65,14 @@ config = MOIT.Config() MOIU.mock_optimize!(mock, [4, 5, 1]) end, ) - MOIT.int1test(bridged_mock, config) + MOIT.test_integer_integration(bridged_mock, config) MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0; zeros(10)]), ) - MOIT.int3test(bridged_mock, config) + MOIT.test_integer_solve_twice(bridged_mock, config) ci = first( MOI.get( diff --git a/test/Bridges/Objective/functionize.jl b/test/Bridges/Objective/functionize.jl index 0ba095809f..72df7e9598 100644 --- a/test/Bridges/Objective/functionize.jl +++ b/test/Bridges/Objective/functionize.jl @@ -13,13 +13,13 @@ config = MOIT.Config() bridged_mock = MOIB.Objective.Functionize{Float64}(mock) -@testset "solve_singlevariable_obj" begin +@testset "test_ObjectiveFunction_SingleVariable" begin MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0], MOI.FEASIBLE_POINT), ) - MOIT.solve_singlevariable_obj(bridged_mock, config) + MOIT.test_ObjectiveFunction_SingleVariable(bridged_mock, config) @test MOI.get(mock, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64} @test MOI.get(bridged_mock, MOI.ObjectiveFunctionType()) == @@ -42,7 +42,7 @@ end # Tests that the `ObjectiveValue` attribute passed has the correct # `result_index`. -@testset "solve_result_index" begin +@testset "test_result_index" begin MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -53,5 +53,5 @@ end (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], ), ) - MOIT.solve_result_index(bridged_mock, config) + MOIT.test_result_index(bridged_mock, config) end diff --git a/test/Bridges/Objective/slack.jl b/test/Bridges/Objective/slack.jl index db7c668db1..984fc47696 100644 --- a/test/Bridges/Objective/slack.jl +++ b/test/Bridges/Objective/slack.jl @@ -22,7 +22,7 @@ bridged_mock = MOIB.Objective.Slack{Float64}(mock) @test_throws err MOI.set(bridged_mock, MOI.ObjectiveFunction{F}(), zero(F)) end -@testset "solve_qp_edge_cases" begin +@testset "test_qp_ObjectiveFunction_edge_cases" begin MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -46,7 +46,7 @@ end (MOI.FEASIBLE_POINT, [1.0, 2.0, 7.0]), ), ) - MOIT.solve_qp_edge_cases(bridged_mock, config) + MOIT.test_qp_ObjectiveFunction_edge_cases(bridged_mock, config) @test MOIB.is_objective_bridged(bridged_mock) @test MOI.get(bridged_mock, MOI.ObjectiveFunctionType()) == @@ -213,7 +213,7 @@ end (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [5 / 7, 6 / 7], ), ) - MOIT.qp1test(bridged_mock, config) + MOIT.test_quadratic_integration(bridged_mock, config) end @testset "QP2" begin MOIU.set_mock_optimize!( @@ -229,7 +229,7 @@ end (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [10 / 7, 12 / 7], ), ) - MOIT.qp2test(bridged_mock, config) + MOIT.test_quadratic_duplicate_terms(bridged_mock, config) end @testset "QP3" begin MOIU.set_mock_optimize!( @@ -249,6 +249,6 @@ end (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0], ), ) - MOIT.qp3test(bridged_mock, config) + MOIT.test_quadratic_nonhomogeneous(bridged_mock, config) end end diff --git a/test/Bridges/Variable/flip_sign.jl b/test/Bridges/Variable/flip_sign.jl index 4101109d14..df0f46397c 100644 --- a/test/Bridges/Variable/flip_sign.jl +++ b/test/Bridges/Variable/flip_sign.jl @@ -22,7 +22,7 @@ config = MOIT.Config() (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[7, 2, -4]], ) - MOIT.lin2vtest(bridged_mock, config) + MOIT.test_conic_linear_VectorOfVariables_2(bridged_mock, config) @test MOI.get(mock, MOI.NumberOfVariables()) == 4 @test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 4 @@ -143,7 +143,7 @@ config = MOIT.Config() MOI.INFEASIBILITY_CERTIFICATE, ), ) - MOIT.lin4test(bridged_mock, config) + MOIT.test_conic_linear_INFEASIBLE_2(bridged_mock, config) @test MOI.get(mock, MOI.NumberOfVariables()) == 1 @test length(MOI.get(mock, MOI.ListOfVariableIndices())) == 1 diff --git a/test/Bridges/Variable/free.jl b/test/Bridges/Variable/free.jl index cb0d540168..cba8105699 100644 --- a/test/Bridges/Variable/free.jl +++ b/test/Bridges/Variable/free.jl @@ -13,7 +13,7 @@ config = MOIT.Config() bridged_mock = MOIB.Variable.Free{Float64}(mock) -@testset "solve_multirow_vectoraffine_nonpos" begin +@testset "test_solve_multirow_vectoraffine_nonpos" begin MOIU.set_mock_optimize!( mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -27,7 +27,7 @@ bridged_mock = MOIB.Variable.Free{Float64}(mock) (MOI.FEASIBLE_POINT, [0.25, 0.0]), ), ) - MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config) + MOIT.test_modification_multirow_vectoraffine_nonpos(bridged_mock, config) MOI.set( bridged_mock, @@ -98,7 +98,10 @@ end [-1.0], ), ) - MOIT.linear6test(bridged_mock, config) + MOIT.test_linear_modify_GreaterThan_and_LessThan_constraints( + bridged_mock, + config, + ) loc = MOI.get(bridged_mock, MOI.ListOfConstraintTypesPresent()) @test length(loc) == 2 @@ -158,7 +161,7 @@ end ) end set_mock_optimize_linear7Test!(mock) - MOIT.linear7test(bridged_mock, config) + MOIT.test_linear_VectorAffineFunction(bridged_mock, config) x, y = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) @@ -195,7 +198,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5, 0.0, 0.0]), ) - MOIT.linear11test(bridged_mock, config) + MOIT.test_linear_transform(bridged_mock, config) vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) @test vis == MOI.VariableIndex.([-1, -2]) diff --git a/test/Bridges/Variable/rsoc_to_psd.jl b/test/Bridges/Variable/rsoc_to_psd.jl index 914c520ea8..51c0f1efb4 100644 --- a/test/Bridges/Variable/rsoc_to_psd.jl +++ b/test/Bridges/Variable/rsoc_to_psd.jl @@ -125,7 +125,7 @@ end [[1.0, -0.5, 0.25, -0.5, 0.25, 0.25]], ) mock.eval_variable_constraint_dual = false - MOIT.rotatedsoc4test(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_out_of_order(bridged_mock, config) mock.eval_variable_constraint_dual = true @testset "Test mock model" begin diff --git a/test/Bridges/Variable/rsoc_to_soc.jl b/test/Bridges/Variable/rsoc_to_soc.jl index 93381160ec..57ca612f40 100644 --- a/test/Bridges/Variable/rsoc_to_soc.jl +++ b/test/Bridges/Variable/rsoc_to_soc.jl @@ -21,7 +21,7 @@ bridged_mock = MOIB.Variable.RSOCtoSOC{Float64}(mock) (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], ) - MOIT.rotatedsoc4test(bridged_mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_out_of_order(bridged_mock, config) ceqs = MOI.get( mock, diff --git a/test/Bridges/Variable/soc_to_rsoc.jl b/test/Bridges/Variable/soc_to_rsoc.jl index a22208fc83..0cb76ea206 100644 --- a/test/Bridges/Variable/soc_to_rsoc.jl +++ b/test/Bridges/Variable/soc_to_rsoc.jl @@ -20,7 +20,7 @@ bridged_mock = MOIB.Variable.SOCtoRSOC{Float64}(mock) [1 / √2 + 1 / 2, 1 / √2 - 1 / 2, 1 / √2], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], ) - MOIT.soc1vtest(bridged_mock, config) + MOIT.test_conic_SecondOrderCone_VectorOfVariables(bridged_mock, config) ceqs = MOI.get( mock, diff --git a/test/Bridges/bridge_optimizer.jl b/test/Bridges/bridge_optimizer.jl index fce8be17d6..c7e1ab3a74 100644 --- a/test/Bridges/bridge_optimizer.jl +++ b/test/Bridges/bridge_optimizer.jl @@ -457,8 +457,12 @@ end end @testset "Continuous Linear" begin - exclude = ["partial_start"] # VariablePrimalStart not supported. - MOIT.contlineartest(bridged_mock, MOIT.Config(solve = false), exclude) + MOI.Test.runtests( + bridged_mock, + MOIT.Config(solve = false), + include = ["test_linear_"], + exclude = ["VariablePrimalStart", "linear_mixed_complementarity"], + ) end @testset "Show" begin diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index 8dfcd2573f..c6ef2abba2 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -332,13 +332,21 @@ end @testset "Unit" begin model = LPModel{Float64}() bridged = MOIB.full_bridge_optimizer(model, Float64) - MOIT.unittest( + MOI.Test.runtests( bridged, MOIT.Config(solve = false), - [ - # SOC and quadratic constraints not supported - "solve_qcp_edge_cases", - "delete_soc_variables", + # TODO(odow): we should reconsider how we test all this. + exclude = [ + "_quadratic_", + "_qcp_", + "_socp_", + "_soc_", + "_SOS1_", + "_SOS2_", + "_Indicator_", + "_nonlinear_", + "_SecondOrderCone", + "_VariablePrimalStart", ], ) end @@ -1152,10 +1160,14 @@ end # is equivalent to # `Constraint.VectorizeBridge` -> `Constraint.VectorSlackBridge` # however, `Variable.VectorizeBridge` do not support modification of the - # set hence it makes some tests of `contlineartest` fail so we disable it. + # set hence it makes some tests fail so we disable it. MOIB.remove_bridge(bridged, MOIB.Constraint.ScalarSlackBridge{T}) - exclude = ["partial_start"] # `VariablePrimalStart` not supported. - MOIT.contlineartest(bridged, MOIT.Config{T}(solve = false), exclude) + MOI.Test.runtests( + bridged, + MOIT.Config{T}(solve = false), + include = ["test_linear_"], + exclude = ["VariablePrimalStart", "linear_mixed_complementarity"], + ) end @testset "SDPAModel with bridges and caching" begin diff --git a/test/Test/Test.jl b/test/Test/Test.jl index 1e26b3d94d..aa3732c373 100644 --- a/test/Test/Test.jl +++ b/test/Test/Test.jl @@ -1,4 +1,8 @@ using Test +using MathOptInterface + +const MOI = MathOptInterface +const MOIU = MOI.Utilities @testset "$(file)" for file in readdir(@__DIR__) if file == "Test.jl" @@ -6,3 +10,27 @@ using Test end include(file) end + +@testset "runtests" begin + MOI.Test.runtests( + MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())), + MOI.Test.Config(basis = true), + exclude = ["test_nonlinear_"], + ) + + MOI.Test.runtests( + MOIU.MockOptimizer( + MOIU.UniversalFallback(MOIU.Model{Float64}()), + eval_objective_value = false, + ), + MOI.Test.Config(optimal_status = MOI.LOCALLY_SOLVED), + include = ["test_nonlinear_"], + ) +end + +@testset "basic_constraint_tests" begin + MOI.Test.basic_constraint_tests( + MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())), + MOI.Test.Config(), + ) +end diff --git a/test/Test/config.jl b/test/Test/config.jl deleted file mode 100644 index e4433fe706..0000000000 --- a/test/Test/config.jl +++ /dev/null @@ -1,28 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -function atest(model::MOI.ModelLike, config::MOIT.Config{T}) where {T<:Real} - @test config.atol == Base.rtoldefault(T) - @test config.rtol == Base.rtoldefault(T) - @test config.solve - @test config.query - @test config.duals - @test config.infeas_certificates -end - -function btest(model::MOI.ModelLike, config::MOIT.Config) - @test false # b is in exclude -end - -const customtests = Dict("a" => atest, "b" => btest) - -MOIT.@moitestset custom - -@testset "Config" begin - mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) - config = MOIT.Config() - customtest(mock, config, ["b"]) -end diff --git a/test/Test/contconic.jl b/test/Test/contconic.jl index 1a77ae6af0..e524ec2945 100644 --- a/test/Test/contconic.jl +++ b/test/Test/contconic.jl @@ -7,201 +7,6 @@ const MOIU = MOI.Utilities mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) config = MOIT.Config() -@testset "Linear" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 0.0, 2.0], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]], - ) - MOIT.lin1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 0.0, 2.0], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[0, 2, 0]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]], - ) - MOIT.lin1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [-4, -3, 16, 0], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[7, 2, -4]], - ) - MOIT.lin2vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [-4, -3, 16, 0], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0]], - (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[0]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => - [[7, 2, -4], [7]], - ) - MOIT.lin2ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - MOI.INFEASIBLE_POINT, - MOI.INFEASIBILITY_CERTIFICATE, - ) - MOIT.lin3test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.INFEASIBLE) - MOIT.lin3test(mock, MOIT.Config(infeas_certificates = false)) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, MOI.INFEASIBLE_OR_UNBOUNDED) - MOIT.lin3test(mock, MOIT.Config(infeas_certificates = false)) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - MOI.INFEASIBLE_POINT, - MOI.INFEASIBILITY_CERTIFICATE, - ) - MOIT.lin4test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.INFEASIBLE) - MOIT.lin4test(mock, MOIT.Config(infeas_certificates = false)) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, MOI.INFEASIBLE_OR_UNBOUNDED) - MOIT.lin4test(mock, MOIT.Config(infeas_certificates = false)) -end -@testset "NormInf" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0.5, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [-1]], - (MOI.VectorOfVariables, MOI.NormInfinityCone) => [[1, 0, -1]], - ) - MOIT.norminf1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0.5, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [-1]], - (MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) => - [[1, 0, -1]], - ) - MOIT.norminf1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - MOI.INFEASIBLE_POINT, - MOI.INFEASIBILITY_CERTIFICATE, - ) - MOIT.norminf2test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [2, -1, -1, -1], - (MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) => - [vcat(1, fill(-inv(3), 3))], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [fill(inv(3), 3)], - ) - MOIT.norminf3test(mock, config) -end -@testset "NormOne" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0.5, 0.5], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [0]], - (MOI.VectorOfVariables, MOI.NormOneCone) => [[1, -1, -1]], - ) - MOIT.normone1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0.5, 0.5], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [0]], - (MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) => - [[1, -1, -1]], - ) - MOIT.normone1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - MOI.INFEASIBLE_POINT, - MOI.INFEASIBILITY_CERTIFICATE, - ) - MOIT.normone2test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [4, -1, -1, -1], - (MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) => - [vcat(1, fill(-1, 3))], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [ones(3)], - ) - MOIT.normone3test(mock, config) -end -@testset "SOC" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 1 / √2, 1 / √2], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], - ) - MOIT.soc1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 1 / √2, 1 / √2], - (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => - [[√2, -1, -1]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]], - ) - MOIT.soc1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [-1 / √2, 1 / √2, 1.0], - (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => - [[√2, 1, -1]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[√2]], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[1.0]], - ) - MOIT.soc2ntest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [-1 / √2, 1 / √2, 1.0], - (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => - [[√2, 1, -1]], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[√2]], - (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => - [[-1.0]], - ) - MOIT.soc2ptest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - MOI.INFEASIBLE_POINT, - MOI.INFEASIBILITY_CERTIFICATE, - ) - MOIT.soc3test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 2 / √5, 1 / √5, 2 / √5, 1 / √5], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => - [[-√5, -2.0, -1.0]], - ) - MOIT.soc4test(mock, config) -end @testset "RSOC" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -213,7 +18,7 @@ end ) # double variable bounds on a and b variables mock.eval_variable_constraint_dual = false - MOIT.rotatedsoc1vtest(mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorOfVariables(mock, config) mock.eval_variable_constraint_dual = true mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -221,7 +26,7 @@ end [1 / √2, 1 / √2], (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[√2, 1 / √2, -1.0, -1.0]], ) - MOIT.rotatedsoc1ftest(mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_VectorAffineFunction(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, @@ -258,7 +63,7 @@ end ) # double variable bounds on u mock.eval_variable_constraint_dual = false - MOIT.rotatedsoc3test(mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_INFEASIBLE(mock, config) mock.eval_variable_constraint_dual = true mock.optimize! = @@ -270,8 +75,9 @@ end (MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) => [[1.0, 1.0, -1.0, -1.0]], ) - MOIT.rotatedsoc4test(mock, config) + MOIT.rotatedtest_conic_SecondOrderCone_out_of_order(mock, config) end + @testset "GeoMean" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( @@ -328,105 +134,7 @@ end ) MOIT.geomean3ftest(mock, config) end -@testset "Exponential" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 2.0, 2exp(1 / 2)], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [1 + exp(1 / 2), 1 + exp(1 / 2) / 2], - ) - MOIT.exp1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 2.0, 2exp(1 / 2)], - (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => - [[-exp(1 / 2), -exp(1 / 2) / 2, 1.0]], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [1 + exp(1 / 2), 1 + exp(1 / 2) / 2], - ) - MOIT.exp1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.0, -0.3, 0.0, exp(-0.3), exp(-0.3), exp(-0.3), 0.0, 1.0, 0.0], - (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [ - [-exp(-0.3) / 2, -1.3exp(-0.3) / 2, 0.5], - [-exp(-0.3) / 2, -1.3exp(-0.3) / 2, 0.5], - ], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0, exp(-0.3) * 0.3], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [-exp(-0.3) * 0.3], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [ - [0.0, exp(-0.3), exp(-0.3) / 2], - [0.0, 0.0, exp(-0.3) / 2], - ], - ) - MOIT.exp2test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [log(5), 5.0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [0.0], - (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => - [[-1.0, log(5) - 1, 1 / 5]], - ) - MOIT.exp3test(mock, config) -end -@testset "Dual Exponential" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [ - -exp(1 / 2), - -exp(1 / 2) / 2, - 1.0, - 1 + exp(1 / 2), - 1 + exp(1 / 2) / 2, - ], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0, -2.0, -2exp(1 / 2)], - ) - MOIT.dualexp1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [ - -exp(1 / 2), - -exp(1 / 2) / 2, - 1.0, - 1 + exp(1 / 2), - 1 + exp(1 / 2) / 2, - ], - (MOI.VectorAffineFunction{Float64}, MOI.DualExponentialCone) => - [[1.0, 2.0, 2exp(1 / 2)]], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0, -2.0, -2exp(1 / 2)], - ) - MOIT.dualexp1ftest(mock, config) -end -@testset "Dual Power" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.9, 0.1, 1.0, -0.9, -0.1], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0, -1.0, 1.0], - ) - MOIT.dualpow1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.9, 0.1, 1.0, -0.9, -0.1], - (MOI.VectorAffineFunction{Float64}, MOI.DualPowerCone{Float64}) => [[1.0, 1.0, -1.0]], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0, -1.0, 1.0], - ) - MOIT.dualpow1ftest(mock, config) -end + @testset "PSD" begin # PSD0 mock.optimize! = @@ -575,129 +283,3 @@ end ) MOIT.psds3test(mock, config) end -@testset "LogDet" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 1, 0, 1, 1], - (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[1, 1]], - (MOI.VectorOfVariables, MOI.LogDetConeTriangle) => - [[-1, -2, 1, 0, 1]], - ) - mock.eval_variable_constraint_dual = false - MOIT.logdett1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 1, 0, 1, 1], - (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[1, 1]], - (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle) => - [[-1, -2, 1, 0, 1]], - ) - MOIT.logdett1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [log(5)], - (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeTriangle) => - [[-1, log(5) - 3, 1, -1, 1.6, 0, -0.2, 0.4]], - ) - MOIT.logdett2test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 1, 0, 0, 1, 1], - (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[1, 1]], - (MOI.VectorOfVariables, MOI.LogDetConeSquare) => - [[-1, -2, 1, 0, 0, 1]], - ) - mock.eval_variable_constraint_dual = false - MOIT.logdets1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 1, 0, 0, 1, 1], - (MOI.SingleVariable, MOI.EqualTo{Float64}) => [2], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[1, 1]], - (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeSquare) => - [[-1, -2, 1, 0, 0, 1]], - ) - MOIT.logdets1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [log(5)], - (MOI.VectorAffineFunction{Float64}, MOI.LogDetConeSquare) => - [[-1, log(5) - 3, 1, -1, 0, -1, 1.6, -0.2, 0, -0.2, 0.4]], - ) - MOIT.logdets2test(mock, config) -end -@testset "RootDet" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 1, 0, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[0.5, 0.5]], - (MOI.VectorOfVariables, MOI.RootDetConeTriangle) => - [[-1.0, 0.5, 0.0, 0.5]], - ) - MOIT.rootdett1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 1, 0, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[0.5, 0.5]], - (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle) => - [[-1.0, 0.5, 0.0, 0.5]], - ) - MOIT.rootdett1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [5^inv(3)], - (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle) => - [vcat(-1, [1, -1, 1.6, 0, -0.2, 0.4] / 3 * (5^inv(3)))], - ) - MOIT.rootdett2test(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 1, 0, 0, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[0.5, 0.5]], - (MOI.VectorOfVariables, MOI.RootDetConeSquare) => - [[-1.0, 0.5, 0.0, 0.0, 0.5]], - ) - MOIT.rootdets1vtest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 1, 0, 0, 1], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [[0.5, 0.5]], - (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeSquare) => - [[-1.0, 0.5, 0.0, 0.0, 0.5]], - ) - MOIT.rootdets1ftest(mock, config) - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [5^inv(3)], - (MOI.VectorAffineFunction{Float64}, MOI.RootDetConeSquare) => [ - vcat( - -1, - [1, -1, 0, -1, 1.6, -0.2, 0, -0.2, 0.4] / 3 * (5^inv(3)), - ), - ], - ) - MOIT.rootdets2test(mock, config) -end diff --git a/test/Test/contlinear.jl b/test/Test/contlinear.jl deleted file mode 100644 index 594a0ed342..0000000000 --- a/test/Test/contlinear.jl +++ /dev/null @@ -1,316 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) -config = MOIT.Config(basis = true) -config_no_lhs_modif = MOIT.Config(modify_lhs = false) - -function set_mock_optimize_linear1Test!(mock) - return MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [-1], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [-1], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 0, 1], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [-2], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-1, 0, 2]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 2, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.5], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [0.5], - ), - ) -end -set_mock_optimize_linear1Test!(mock) -MOIT.linear1test(mock, config) -set_mock_optimize_linear1Test!(mock) -MOIT.linear1test(mock, config_no_lhs_modif) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [MOI.NONBASIC], - ], - var_basis = [MOI.BASIC, MOI.NONBASIC_AT_LOWER], - ), -) -MOIT.linear2test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [3], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [MOI.NONBASIC], - ], - var_basis = [MOI.BASIC], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [MOI.BASIC], - ], - var_basis = [MOI.NONBASIC_AT_UPPER], - ), -) -MOIT.linear3test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), -) -MOIT.linear4test(mock, config) -function set_mock_optimize_linear5Test!(mock) - return MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4 / 3, 4 / 3]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2]), - ) -end -set_mock_optimize_linear5Test!(mock) -MOIT.linear5test(mock, config) -set_mock_optimize_linear5Test!(mock) -MOIT.linear5test(mock, config_no_lhs_modif) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), -) -MOIT.linear6test(mock, config) -function set_mock_optimize_linear7Test!(mock) - return MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100]), - ) -end -set_mock_optimize_linear7Test!(mock) -MOIT.linear7test(mock, config) -set_mock_optimize_linear7Test!(mock) -MOIT.linear7test(mock, config_no_lhs_modif) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - tuple(), - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - ), -) -MOIT.linear8atest(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.INFEASIBLE), -) -MOIT.linear8atest(mock, MOIT.Config(infeas_certificates = false)) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.DUAL_INFEASIBLE, - MOI.INFEASIBILITY_CERTIFICATE, - ), -) -MOIT.linear8btest(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE), -) -MOIT.linear8btest(mock, MOIT.Config(infeas_certificates = false)) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.DUAL_INFEASIBLE, - (MOI.INFEASIBILITY_CERTIFICATE, [1, 1]), - ), -) -MOIT.linear8ctest(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE), -) -MOIT.linear8ctest(mock, MOIT.Config(infeas_certificates = false)) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [650 / 11, 400 / 11], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [MOI.NONBASIC, MOI.NONBASIC], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [MOI.BASIC], - ], - var_basis = [MOI.BASIC, MOI.BASIC], - ), -) -MOIT.linear9test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [5.0, 5.0], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => - [MOI.NONBASIC_AT_UPPER], - ], - var_basis = [MOI.BASIC, MOI.BASIC], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [2.5, 2.5], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => - [MOI.NONBASIC_AT_LOWER], - ], - var_basis = [MOI.BASIC, MOI.BASIC], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [1], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [1], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => - [MOI.NONBASIC_AT_LOWER], - ], - var_basis = [MOI.BASIC, MOI.BASIC], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [6.0, 6.0], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => - [MOI.NONBASIC_AT_UPPER], - ], - var_basis = [MOI.BASIC, MOI.BASIC], - ), -) -MOIT.linear10test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.0, 0.0], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => - [MOI.BASIC], - ], - var_basis = [MOI.NONBASIC_AT_LOWER, MOI.NONBASIC_AT_LOWER], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [0], - ), -) -MOIT.linear10btest(mock, config) - -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5]), -) -MOIT.linear11test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - tuple(), - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [-1, -1], - ), -) -MOIT.linear12test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.INFEASIBLE), -) -MOIT.linear12test(mock, MOIT.Config(infeas_certificates = false)) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1 / 5, 1 / 5], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [0], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [0], - ), -) -MOIT.linear13test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0, 1 / 2, 1], - con_basis = [ - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => - [MOI.NONBASIC], - ], - var_basis = [MOI.NONBASIC_AT_LOWER, MOI.BASIC, MOI.NONBASIC_AT_UPPER], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2, 0, 0], - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0], - ), -) -# linear14 has double variable bounds for the z variable -mock.eval_variable_constraint_dual = false -MOIT.linear14test(mock, config) -mock.eval_variable_constraint_dual = true -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.0], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[0.0, 0.0]], - ), -) -MOIT.linear15test(mock, config) - -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0]), -) -MOIT.partial_start_test(mock, config) diff --git a/test/Test/contquadratic.jl b/test/Test/contquadratic.jl deleted file mode 100644 index 6faf97e45f..0000000000 --- a/test/Test/contquadratic.jl +++ /dev/null @@ -1,125 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) -config = MOIT.Config() - -@testset "QP" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [4 / 7, 3 / 7, 6 / 7], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [5 / 7, 6 / 7], - ), - ) - MOIT.qp1test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [4 / 7, 3 / 7, 6 / 7], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [5 / 7, 6 / 7], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [4 / 7, 3 / 7, 6 / 7], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [10 / 7, 12 / 7], - ), - ) - MOIT.qp2test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1 / 4, 3 / 4], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [11 / 4], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 0.0], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-2.0], - ), - ) - MOIT.qp3test(mock, config) -end -@testset "QCP" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1 / 2, 7 / 4], - (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => - [zeros(2)], - (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => - [-1.0], - ), - ) - MOIT.qcp1test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [√2], - (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => - [-1 / (2 * √2)], - ), - ) - MOIT.qcp2test(mock, config) - MOIT.qcp3test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 1.0], - (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => - [-1 / 3], - ), - ) - MOIT.qcp4test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1.0, 1.0], - (MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}) => [1 / 3], - ), - ) - MOIT.qcp5test(mock, config) -end -@testset "Non-convex QCP" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [4.0, 1.0], MOI.FEASIBLE_POINT), - ) - MOIT.ncqcp1test(mock, config) - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [2.0, 2.0], MOI.FEASIBLE_POINT), - ) - MOIT.ncqcp2test(mock, config) -end -@testset "SOCP" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [1 / 2, 1 / 2, 1 / √2], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => - [1 / √2], - (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => - [-1 / √2], - ), - ) - MOIT.socp1test(mock, config) -end diff --git a/test/Test/intconic.jl b/test/Test/intconic.jl deleted file mode 100644 index 08d18cbfeb..0000000000 --- a/test/Test/intconic.jl +++ /dev/null @@ -1,14 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) -config = MOIT.Config() - -@testset "SOC" begin - mock.optimize! = - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 0.0]) - MOIT.intsoc1test(mock, config) -end diff --git a/test/Test/intlinear.jl b/test/Test/intlinear.jl deleted file mode 100644 index 0416541a70..0000000000 --- a/test/Test/intlinear.jl +++ /dev/null @@ -1,118 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) -config = MOIT.Config() - -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 20.0) - MOIU.mock_optimize!(mock, [4, 5, 1]) - end, -) -MOIT.int1test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1, 2]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 2]), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.0, 12.0], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - [0.0, 0.0, 2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 6.0, 24.0], - ), -) -MOIT.int2test(mock, config) -# FIXME [1, 0...] is not the correct optimal solution but it passes the test -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0; zeros(10)]), -) -MOIT.int3test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0, 1, 1]), -) -MOIT.knapsacktest(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [1.25, 8.75, 0.0, 1.0]), -) -MOIT.indicator1_test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [2.0, 8.0, 1.0, 0.0]), -) -MOIT.indicator2_test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [1.25, 8.75, 1.0, 1.0]), -) -MOIT.indicator3_test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> - MOIU.mock_optimize!(mock, [1.25, 8.75, 0.0, 1.0]), -) -MOIT.indicator4_test(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 0.0) - MOIU.mock_optimize!(mock, [0.0, 0.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.0) - MOIU.mock_optimize!(mock, [2.0, 1.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.0) - MOIU.mock_optimize!(mock, [2.0, 2.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.5) - MOIU.mock_optimize!(mock, [2.5, 2.5]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 3.0) - MOIU.mock_optimize!(mock, [3.0, 3.0]) - end, - (mock::MOIU.MockOptimizer) -> - MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), -) -MOIT.semiconttest(mock, config) -MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 0.0) - MOIU.mock_optimize!(mock, [0.0, 0.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.0) - MOIU.mock_optimize!(mock, [2.0, 1.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.0) - MOIU.mock_optimize!(mock, [2.0, 2.0]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 3.0) - MOIU.mock_optimize!(mock, [3.0, 2.5]) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 3.0) - MOIU.mock_optimize!(mock, [3.0, 3.0]) - end, - (mock::MOIU.MockOptimizer) -> - MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE), -) -MOIT.semiinttest(mock, config) diff --git a/test/Test/nlp.jl b/test/Test/nlp.jl deleted file mode 100644 index d2b931d261..0000000000 --- a/test/Test/nlp.jl +++ /dev/null @@ -1,108 +0,0 @@ -using Test - -import MathOptInterface -const MOI = MathOptInterface - -@testset "hs071-manual" begin - d = MOI.Test.HS071(true, true) - MOI.initialize(d, [:Grad, :Jac, :ExprGraph, :Hess, :HessVec]) - @test :HessVec in MOI.features_available(d) - x = ones(4) - # f(x) - @test MOI.eval_objective(d, x) == 4.0 - # g(x) - g = zeros(2) - MOI.eval_constraint(d, g, x) - @test g == [1.0, 4.0] - # f'(x) - ∇f = fill(NaN, length(x)) - MOI.eval_objective_gradient(d, ∇f, x) - @test ∇f == [4.0, 1.0, 2.0, 3.0] - # Jacobian - Js = MOI.jacobian_structure(d) - J = fill(NaN, length(Js)) - MOI.eval_constraint_jacobian(d, J, x) - @test J == [1, 1, 1, 1, 2, 2, 2, 2] - # Hessian-lagrangian - Hs = MOI.hessian_lagrangian_structure(d) - H = fill(NaN, length(Hs)) - MOI.eval_hessian_lagrangian(d, H, x, 1.0, [1.0, 1.0]) - @test H == [4, 2, 2, 2, 1, 2, 5, 2, 2, 2] - # Hessian-lagrangian-product - Hv = fill(NaN, length(x)) - v = [1.0, 1.1, 1.2, 1.3] - MOI.eval_hessian_lagrangian_product(d, Hv, x, v, 1.0, [1.0, 1.0]) - @test Hv == [15.1, 8.0, 8.1, 12.2] -end - -@testset "hs071" begin - mock = MOI.Utilities.MockOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - eval_objective_value = false, - ) - config = MOI.Test.Config(optimal_status = MOI.LOCALLY_SOLVED) - MOI.Utilities.set_mock_optimize!( - mock, - (mock) -> begin - MOI.Utilities.mock_optimize!( - mock, - config.optimal_status, - [ - 1.0, - 4.7429996418092970, - 3.8211499817883077, - 1.379408289755698, - ], - ) - MOI.set(mock, MOI.ObjectiveValue(), 17.014017145179164) - end, - ) - MOI.Test.hs071_test(mock, config) - MOI.Test.hs071_no_hessian_test(mock, config) - MOI.Test.hs071_hessian_vector_product_test(mock, config) - - d = MOI.Test.HS071(false) - VI = MOI.VariableIndex - @test MOI.objective_expr(d) == :( - x[$(VI(1))] * x[$(VI(4))] * (x[$(VI(1))] + x[$(VI(2))] + x[$(VI(3))]) + - x[$(VI(3))] - ) - @test MOI.constraint_expr(d, 1) == - :(x[$(VI(1))] * x[$(VI(2))] * x[$(VI(3))] * x[$(VI(4))] >= 25.0) - @test MOI.constraint_expr(d, 2) == :( - x[$(VI(1))]^2 + x[$(VI(2))]^2 + x[$(VI(3))]^2 + x[$(VI(4))]^2 == 40.0 - ) - @test_throws ErrorException MOI.constraint_expr(d, 3) -end - -@testset "mixed_complementarity" begin - mock = MOI.Utilities.MockOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - ) - config = MOI.Test.Config(optimal_status = MOI.LOCALLY_SOLVED) - MOI.Utilities.set_mock_optimize!( - mock, - (mock) -> MOI.Utilities.mock_optimize!( - mock, - config.optimal_status, - [2.8, 0.0, 0.8, 1.2], - ), - ) - MOI.Test.mixed_complementaritytest(mock, config) -end - -@testset "math_program_complementarity_constraints" begin - mock = MOI.Utilities.MockOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - ) - config = MOI.Test.Config(optimal_status = MOI.LOCALLY_SOLVED) - MOI.Utilities.set_mock_optimize!( - mock, - (mock) -> MOI.Utilities.mock_optimize!( - mock, - config.optimal_status, - [1.0, 0.0, 3.5, 0.0, 0.0, 0.0, 3.0, 6.0], - ), - ) - MOI.Test.math_program_complementarity_constraintstest(mock, config) -end diff --git a/test/Test/unit.jl b/test/Test/unit.jl deleted file mode 100644 index 961b3476bc..0000000000 --- a/test/Test/unit.jl +++ /dev/null @@ -1,867 +0,0 @@ -using Test -import MathOptInterface -const MOI = MathOptInterface -const MOIT = MOI.Test -const MOIU = MOI.Utilities - -@testset "Basic Constraint Tests" begin - mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) - config = MOIT.Config() - MOIT.basic_constraint_tests(mock, config) -end - -@testset "Unit Tests" begin - # `UniversalFallback` needed for `MOI.Silent` - mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) - # Optimizers attributes have to be set to default value since the mock - # optimizer doesn't handle this - MOI.set(mock, MOI.Silent(), true) - MOI.set(mock, MOI.TimeLimitSec(), nothing) - MOI.set(mock, MOI.NumberOfThreads(), nothing) - config = MOIT.Config() - for model in [ - mock, - MOIU.CachingOptimizer( - MOIU.UniversalFallback(MOIU.Model{Float64}()), - mock, - ), - ] - MOIT.unittest( - model, - config, - [ - "solve_blank_obj", - "solve_constant_obj", - "solve_singlevariable_obj", - "solve_with_lowerbound", - "solve_with_upperbound", - "solve_affine_lessthan", - "solve_affine_greaterthan", - "solve_affine_equalto", - "solve_affine_interval", - "solve_duplicate_terms_scalar_affine", - "solve_duplicate_terms_vector_affine", - "solve_qp_edge_cases", - "solve_qp_zero_offdiag", - "solve_qcp_edge_cases", - "solve_affine_deletion_edge_cases", - "solve_duplicate_terms_obj", - "solve_integer_edge_cases", - "solve_objbound_edge_cases", - "raw_status_string", - "solve_time", - "solve_zero_one_with_bounds_1", - "solve_zero_one_with_bounds_2", - "solve_zero_one_with_bounds_3", - "solve_start_soc", - "solve_unbounded_model", - "solve_single_variable_dual_min", - "solve_single_variable_dual_max", - "solve_result_index", - "solve_farkas_equalto_lower", - "solve_farkas_equalto_upper", - "solve_farkas_lessthan", - "solve_farkas_greaterthan", - "solve_farkas_interval_lower", - "solve_farkas_interval_upper", - "solve_farkas_variable_lessthan", - "solve_farkas_variable_lessthan_max", - "solve_twice", - ], - ) - MOI.empty!(model) - end - - @testset "solve_blank_obj" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_blank_obj(mock, config) - # The objective is blank so any primal value ≥ 1 is correct - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_blank_obj(mock, config) - end - @testset "solve_constant_obj" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_constant_obj(mock, config) - end - @testset "solve_singlevariable_obj" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_singlevariable_obj(mock, config) - end - @testset "solve_with_lowerbound" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0], - (MOI.SingleVariable, MOI.LessThan{Float64}) => [0.0], - ), - ) - # x has two variable constraints - mock.eval_variable_constraint_dual = false - MOIT.solve_with_lowerbound(mock, config) - mock.eval_variable_constraint_dual = true - end - @testset "solve_with_upperbound" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0], - ), - ) - # x has two variable constraints - mock.eval_variable_constraint_dual = false - MOIT.solve_with_upperbound(mock, config) - mock.eval_variable_constraint_dual = true - end - @testset "solve_affine_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-0.5], - ), - ) - MOIT.solve_affine_lessthan(mock, config) - end - @testset "solve_affine_greaterthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0.5], - ), - ) - MOIT.solve_affine_greaterthan(mock, config) - end - @testset "solve_affine_equalto" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [0.5], - ), - ) - MOIT.solve_affine_equalto(mock, config) - end - @testset "solve_affine_interval" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1.5], - ), - ) - MOIT.solve_affine_interval(mock, config) - end - @testset "solve_duplicate_terms_scalar_affine" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-0.5], - ), - ) - MOIT.solve_duplicate_terms_scalar_affine(mock, config) - end - @testset "solve_duplicate_terms_vector_affine" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => - [[-0.5]], - ), - ) - MOIT.solve_duplicate_terms_vector_affine(mock, config) - end - - @testset "solve_qcp_edge_cases" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5, 0.5]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5, (√13 - 1) / 4]), - ), - ) - MOIT.solve_qcp_edge_cases(mock, config) - end - - @testset "solve_qp_edge_cases" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]), - ), - ) - MOIT.solve_qp_edge_cases(mock, config) - end - @testset "solve_qp_zero_offdiag" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 2.0]), - ), - ) - MOIT.solve_qp_zero_offdiag(mock, config) - end - @testset "solve_duplicate_terms_obj" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_duplicate_terms_obj(mock, config) - end - @testset "solve_affine_deletion_edge_cases" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - ), - ) - MOIT.solve_affine_deletion_edge_cases(mock, config) - end - @testset "solve_integer_edge_cases" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0]), - ), - ) - MOIT.solve_integer_edge_cases(mock, config) - end - @testset "solve_objbound_edge_cases" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 3.0) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - ) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 3.0) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 2.0) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.5]), - ) - end, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.ObjectiveBound(), 4.0) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.5]), - ) - end, - ) - MOIT.solve_objbound_edge_cases(mock, config) - end - - @testset "raw_status_string" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set( - mock, - MOI.RawStatusString(), - "Mock solution set by `mock_optimize!`.", - ) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0]), - ) - end, - ) - MOIT.raw_status_string(mock, config) - end - - @testset "solve_time" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOI.set(mock, MOI.SolveTimeSec(), 0.0) - MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0]), - ) - end, - ) - MOIT.solve_time(mock, config) - end - - @testset "solve_zero_one_with_bounds" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0])) - end, - (mock::MOIU.MockOptimizer) -> begin - MOIU.mock_optimize!(mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0])) - end, - (mock::MOIU.MockOptimizer) -> begin - MOIU.mock_optimize!(mock, MOI.INFEASIBLE) - end, - ) - MOIT.solve_zero_one_with_bounds_1(mock, config) - MOIT.solve_zero_one_with_bounds_2(mock, config) - MOIT.solve_zero_one_with_bounds_3(mock, config) - end - - @testset "solve_start_soc" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => - [[1.0, -1.0]], - ), - ) - MOIT.solve_start_soc(mock, config) - end - - @testset "solve_unbounded_model" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> begin - MOIU.mock_optimize!(mock, MOI.DUAL_INFEASIBLE) - end, - ) - MOIT.solve_unbounded_model(mock, config) - end - - @testset "solve_single_variable_dual_min" begin - flag = mock.eval_variable_constraint_dual - mock.eval_variable_constraint_dual = false - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], - (MOI.SingleVariable, MOI.LessThan{Float64}) => [0.0], - ), - ) - MOIT.solve_single_variable_dual_min(mock, config) - mock.eval_variable_constraint_dual = flag - end - - @testset "solve_single_variable_dual_max" begin - flag = mock.eval_variable_constraint_dual - mock.eval_variable_constraint_dual = false - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0], - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1.0], - ), - ) - MOIT.solve_single_variable_dual_max(mock, config) - mock.eval_variable_constraint_dual = flag - end - - @testset "solve_result_index" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], - ), - ) - MOIT.solve_result_index(mock, config) - end - - @testset "solve_farkas_equalto_upper" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [-1.0], - ), - ) - MOIT.solve_farkas_equalto_upper(mock, config) - end - - @testset "solve_farkas_equalto_lower" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => - [1.0], - ), - ) - MOIT.solve_farkas_equalto_lower(mock, config) - end - - @testset "solve_farkas_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], - ), - ) - MOIT.solve_farkas_lessthan(mock, config) - end - - @testset "solve_farkas_greaterthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0], - ), - ) - MOIT.solve_farkas_greaterthan(mock, config) - end - - @testset "solve_farkas_interval_upper" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [-1.0], - ), - ) - MOIT.solve_farkas_interval_upper(mock, config) - end - - @testset "solve_farkas_interval_lower" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => - [2.0, 1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}) => [1.0], - ), - ) - MOIT.solve_farkas_interval_lower(mock, config) - end - - @testset "solve_farkas_variable_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0, -1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0], - ), - ) - MOIT.solve_farkas_variable_lessthan(mock, config) - end - - @testset "solve_farkas_variable_lessthan_max" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.INFEASIBLE, - (MOI.NO_SOLUTION, [NaN, NaN]), - MOI.INFEASIBILITY_CERTIFICATE, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-2.0, -1.0], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0], - ), - ) - MOIT.solve_farkas_variable_lessthan_max(mock, config) - end - - @testset "solve_twice" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - ) - MOIT.solve_twice(mock, config) - end -end - -@testset "modifications" begin - # `UniversalFallback` needed for `MOI.Silent` - mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) - config = MOIT.Config() - @testset "delete_variables_in_a_batch" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 1.0, 1.0]), - MOI.FEASIBLE_POINT, - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.delete_variables_in_a_batch(mock, config) - end - @testset "solve_set_singlevariable_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_set_singlevariable_lessthan(mock, config) - end - @testset "solve_transform_singlevariable_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - MOI.FEASIBLE_POINT, - ), - ) - MOIT.solve_transform_singlevariable_lessthan(mock, config) - end - @testset "solve_set_scalaraffine_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [2.0]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], - ), - ) - MOIT.solve_set_scalaraffine_lessthan(mock, config) - end - @testset "solve_coef_scalaraffine_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-0.5], - ), - ) - MOIT.solve_coef_scalaraffine_lessthan(mock, config) - end - @testset "solve_func_scalaraffine_lessthan" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0], - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - MOI.FEASIBLE_POINT, - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-0.5], - ), - ) - MOIT.solve_func_scalaraffine_lessthan(mock, config) - end - @testset "solve_const_vectoraffine_nonpos" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.0, 0.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0, 0.75]), - ), - ) - MOIT.solve_const_vectoraffine_nonpos(mock, config) - end - @testset "solve_multirow_vectoraffine_nonpos" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.5]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [0.25]), - ), - ) - MOIT.solve_multirow_vectoraffine_nonpos(mock, config) - end - @testset "solve_const_scalar_objective" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - ) - MOIT.solve_const_scalar_objective(mock, config) - end - @testset "solve_coef_scalar_objective" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - ), - ) - MOIT.solve_coef_scalar_objective(mock, config) - end - @testset "delete_variable_with_single_variable_obj" begin - MOIU.set_mock_optimize!( - mock, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - MOI.OPTIMAL, - (MOI.FEASIBLE_POINT, [1.0]), - MOI.FEASIBLE_POINT, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], - ), - ) - MOIT.delete_variable_with_single_variable_obj(mock, config) - end -end diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 66ceb85692..7c13cbcbb3 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -566,13 +566,12 @@ for state in (MOIU.NO_OPTIMIZER, MOIU.EMPTY_OPTIMIZER, MOIU.ATTACHED_OPTIMIZER) end config = MOIT.Config(solve = false) - @testset "Unit" begin - MOIT.unittest(m, config) - end - @testset "Continuous Linear" begin - exclude = ["partial_start"] # VariablePrimalStart not supported. - MOIT.contlineartest(m, config, exclude) - end + MOI.Test.runtests( + m, + config, + include = ["test_linear_"], + exclude = ["VariablePrimalStart", "linear_mixed_complementarity"], + ) end end diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index cb83a2b063..bbf44ef6b0 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -187,7 +187,7 @@ MOIU.@product_of_sets( F = MOI.ScalarAffineFunction{Float64} @testset "$SetType" for SetType in [MixLP{Float64}, OrdLP{Float64}] _test( - MOIT.linear2test, + MOIT.test_linear_integration_2, MOI.Utilities.Box{Float64}, SetType, A2, @@ -299,7 +299,7 @@ MOIU.@product_of_sets(NonnegNonpos, MOI.Nonnegatives, MOI.Nonpositives) b = [-1.0, 1.0] F = MOI.VectorAffineFunction{Float64} _test( - MOIT.lin3test, + MOIT.test_conic_linear_INFEASIBLE, Vector{Float64}, NonnegNonpos{Float64}, A, @@ -314,7 +314,7 @@ MOIU.@product_of_sets(NonnegNonpos, MOI.Nonnegatives, MOI.Nonpositives) end b = [1.0, -1.0] _test( - MOIT.lin3test, + MOIT.test_conic_linear_INFEASIBLE, Vector{Float64}, NonposNonneg{Float64}, A, @@ -331,7 +331,7 @@ MOIU.@product_of_sets(NonnegNonpos, MOI.Nonnegatives, MOI.Nonpositives) A = sparse([1, 2], [1, 1], [1.0, -1.0]) b = -ones(2) _test( - MOIT.lin3test, + MOIT.test_conic_linear_INFEASIBLE, Vector{Float64}, Nonneg{Float64}, A, diff --git a/test/Utilities/model.jl b/test/Utilities/model.jl index d1f733b893..b6cf60345c 100644 --- a/test/Utilities/model.jl +++ b/test/Utilities/model.jl @@ -236,14 +236,12 @@ end end @testset "Continuous Linear tests" begin - config = MOIT.Config(solve = false) - exclude = ["partial_start"] # Model doesn't support VariablePrimalStart. - MOIT.contlineartest(MOIU.Model{Float64}(), config, exclude) -end - -@testset "Continuous Conic tests" begin - config = MOIT.Config(solve = false) - MOIT.contconictest(MOIU.Model{Float64}(), config) + MOI.Test.runtests( + MOIU.Model{Float64}(), + MOIT.Config(solve = false), + include = ["test_linear_", "test_conic_"], + exclude = ["VariablePrimalStart", "linear_mixed_complementarity"], + ) end @testset "Quadratic functions" begin diff --git a/test/Utilities/universalfallback.jl b/test/Utilities/universalfallback.jl index 2df241592f..b49913a891 100644 --- a/test/Utilities/universalfallback.jl +++ b/test/Utilities/universalfallback.jl @@ -285,19 +285,10 @@ end @test MOI.get(uf, typeof(cx), "EqualTo") === nothing end end -config = MOIT.Config(solve = false) -@testset "empty" begin - MOI.empty!(uf) - @test MOI.is_empty(uf) -end -@testset "Unit" begin - MOIT.unittest(uf, config) -end -@testset "Modification" begin - MOIT.modificationtest(uf, config) -end -@testset "Continuous Linear" begin - MOIT.contlineartest(uf, config) + +@testset "MOI.Test.runtests" begin + config = MOIT.Config(solve = false) + MOI.Test.runtests(uf, config, exclude = ["_complementarity"]) end @testset "Duplicate names" begin