diff --git a/README.md b/README.md index 9e6d537..5d27ead 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,9 @@ Supported packages include: | [Bonmin](https://github.com/coin-or/Bonmin) | `Bonmin_jll.jl` | `Bomin_jll.amplexe` | | [Couenne](https://github.com/coin-or/Couenne) | `Couenne_jll.jl` | `Couenne_jll.amplexe` | | [Ipopt](https://github.com/coin-or/Ipopt) | `Ipopt_jll.jl` | `Ipopt_jll.amplexe` | -| [SHOT](https://github.com/coin-or/SHOT) | `SHOT_jll.jl` | `SHOT_jll.amplexe` | | [KNITRO](https://github.comjump-dev/KNITRO.jl)| `KNITRO.jl` | `KNITRO.amplexe` | +| [SHOT](https://github.com/coin-or/SHOT) | `SHOT_jll.jl` | `SHOT_jll.amplexe` | +| [Uno](https://github.com/cvanaret/Uno) | `Uno_jll.jl` | `Uno_jll.amplexe` | ## MathOptInterface API diff --git a/test/MINLPTests/Project.toml b/test/MINLPTests/Project.toml index 685a461..bda7e0c 100644 --- a/test/MINLPTests/Project.toml +++ b/test/MINLPTests/Project.toml @@ -4,14 +4,18 @@ Couenne_jll = "f09e9e23-9010-5c9e-b679-9f1d8f79b85c" Ipopt_jll = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MINLPTests = "ee0a3090-8ee9-5cdb-b8cb-8eeba3165522" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" SHOT_jll = "c1ab834c-c4a5-50f5-9156-8f0fe7758b0e" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Uno_jll = "396d5378-14f1-5ab1-981d-48acd51740ed" [compat] Bonmin_jll = "100.800.801" Couenne_jll = "0.500.801" -Ipopt_jll = "=300.1400.400, =300.1400.1400" +Ipopt_jll = "=300.1400.1600" JuMP = "1" +MathOptInterface = "1.33" MINLPTests = "0.6" SHOT_jll = "100.100.0" +Uno_jll = "1.1.0" julia = "1.6" diff --git a/test/MINLPTests/run_minlptests.jl b/test/MINLPTests/run_minlptests.jl index 279dfa4..cf4e497 100644 --- a/test/MINLPTests/run_minlptests.jl +++ b/test/MINLPTests/run_minlptests.jl @@ -3,22 +3,28 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. +using Test + import AmplNLWriter +import Bonmin_jll +import Couenne_jll +import Ipopt_jll +import MathOptInterface as MOI import MINLPTests -using Test +import SHOT_jll +import Uno_jll const TERMINATION_TARGET = Dict( - MINLPTests.FEASIBLE_PROBLEM => AmplNLWriter.MOI.LOCALLY_SOLVED, - MINLPTests.INFEASIBLE_PROBLEM => AmplNLWriter.MOI.LOCALLY_INFEASIBLE, + MINLPTests.FEASIBLE_PROBLEM => MOI.LOCALLY_SOLVED, + MINLPTests.INFEASIBLE_PROBLEM => MOI.LOCALLY_INFEASIBLE, ) const PRIMAL_TARGET = Dict( - MINLPTests.FEASIBLE_PROBLEM => AmplNLWriter.MOI.FEASIBLE_POINT, - MINLPTests.INFEASIBLE_PROBLEM => AmplNLWriter.MOI.NO_SOLUTION, + MINLPTests.FEASIBLE_PROBLEM => MOI.FEASIBLE_POINT, + MINLPTests.INFEASIBLE_PROBLEM => MOI.NO_SOLUTION, ) # Common reasons for exclusion: -# nlp/005_011 : Uses the function `\` # nlp/006_010 : Uses a user-defined function # nlp/007_010 : Ipopt returns an infeasible point, not NO_SOLUTION. # nlp/008_010 : Couenne fails to converge @@ -29,140 +35,188 @@ const PRIMAL_TARGET = Dict( # nlp-cvx/206_010 : Couenne can't evaluate pow # nlp-mi/001_010 : Couenne fails to converge -const CONFIG = Dict{String,Any}() - -import Bonmin_jll -CONFIG["Bonmin"] = Dict( - "amplexe" => Bonmin_jll.amplexe, - "options" => String["bonmin.nlp_log_level=0"], - "tol" => 1e-5, - "dual_tol" => NaN, - "nlp_exclude" => ["005_011", "006_010"], - "nlpcvx_exclude" => ["109_010"], - # 004_010 and 004_011 are tolerance failures on Bonmin - "nlpmi_exclude" => ["004_010", "004_011", "005_011", "006_010"], - "infeasible_point" => AmplNLWriter.MOI.NO_SOLUTION, +const CONFIG = Dict{String,Any}( + "Bonmin" => Dict( + "mixed-integer" => true, + "amplexe" => Bonmin_jll.amplexe, + "options" => String["bonmin.nlp_log_level=0"], + "dual_tol" => NaN, + "nlpcvx_exclude" => ["109_010"], + # 004_010 and 004_011 are tolerance failures on Bonmin + "nlpmi_exclude" => ["004_010", "004_011"], + ), + "Couenne" => Dict( + "mixed-integer" => true, + "amplexe" => Couenne_jll.amplexe, + "options" => String[], + "tol" => 1e-2, + "dual_tol" => NaN, + "nlp_exclude" => ["008_010", "008_011", "009_010", "009_011"], + "nlpcvx_exclude" => ["109_010", "206_010"], + "nlpmi_exclude" => ["001_010"], + ), + "Ipopt" => Dict( + "mixed-integer" => false, + "amplexe" => Ipopt_jll.amplexe, + "options" => String["print_level=0"], + "nlp_exclude" => ["007_010"], + "nlpcvx_exclude" => ["109_010"], + ), + # SHOT fails too many tests to recommend using it. + # e.g., https://github.com/coin-or/SHOT/issues/134 + # Even problems such as `@variable(model, x); @objective(model, Min, (x-1)^2)` + # "SHOT" => Dict( + # "amplexe" => SHOT_jll.amplexe, + # "options" => String[ + # "Output.Console.LogLevel=6", + # "Output.File.LogLevel=6", + # "Termination.ObjectiveGap.Absolute=1e-6", + # "Termination.ObjectiveGap.Relative=1e-6", + # ], + # "tol" => 1e-2, + # "dual_tol" => NaN, + # "infeasible_point" => AmplNLWriter.MOI.UNKNOWN_RESULT_STATUS, + # ), + "Uno" => Dict( + "mixed-integer" => false, + "amplexe" => Uno_jll.amplexe, + "options" => ["logger=SILENT"], + "nlp_exclude" => [ + # See https://github.com/cvanaret/Uno/issues/39 + "005_010", + # See https://github.com/cvanaret/Uno/issues/38 + "007_010", + ], + ), ) -import Couenne_jll -CONFIG["Couenne"] = Dict( - "amplexe" => Couenne_jll.amplexe, - "options" => String[], - "tol" => 1e-2, - "dual_tol" => NaN, - "nlp_exclude" => - ["005_011", "006_010", "008_010", "008_011", "009_010", "009_011"], - "nlpcvx_exclude" => ["109_010", "206_010"], - "nlpmi_exclude" => ["001_010", "005_011", "006_010"], - "infeasible_point" => AmplNLWriter.MOI.NO_SOLUTION, -) - -import Ipopt_jll -CONFIG["Ipopt"] = Dict( - "amplexe" => Ipopt_jll.amplexe, - "options" => String["print_level=0"], - "tol" => 1e-5, - "dual_tol" => 1e-5, - "nlp_exclude" => ["005_011", "006_010", "007_010"], - "nlpcvx_exclude" => ["109_010"], - "nlpmi_exclude" => ["005_011", "006_010"], - "infeasible_point" => AmplNLWriter.MOI.NO_SOLUTION, -) - -# SHOT fails too many tests to recommend using it. -# e.g., https://github.com/coin-or/SHOT/issues/134 -# Even problems such as `@variable(model, x); @objective(model, Min, (x-1)^2)` -# -# import SHOT_jll -# CONFIG["SHOT"] = Dict( -# "amplexe" => SHOT_jll.amplexe, -# "options" => String[ -# "Output.Console.LogLevel=6", -# "Output.File.LogLevel=6", -# "Termination.ObjectiveGap.Absolute=1e-6", -# "Termination.ObjectiveGap.Relative=1e-6", -# ], -# "tol" => 1e-2, -# "dual_tol" => NaN, -# "nlp_exclude" => [ -# "005_011", # `\` function -# "006_010", # User-defined function -# ], -# "nlpcvx_exclude" => [ -# "501_011", # `\` function -# ], -# "nlpmi_exclude" => [ -# "005_011", # `\` function -# "006_010", # User-defined function -# ], -# "infeasible_point" => AmplNLWriter.MOI.UNKNOWN_RESULT_STATUS, -# ) - -@testset "$(name)" for name in ["Ipopt", "Bonmin", "Couenne"] - config = CONFIG[name] +@testset "$k" for (k, config) in CONFIG OPTIMIZER = () -> AmplNLWriter.Optimizer(config["amplexe"], config["options"]) - PRIMAL_TARGET[MINLPTests.INFEASIBLE_PROBLEM] = config["infeasible_point"] + # PRIMAL_TARGET[MINLPTests.INFEASIBLE_PROBLEM] = config["infeasible_point"] @testset "NLP" begin + exclude = vcat(get(config, "nlp_exclude", String[]), ["006_010"]) MINLPTests.test_nlp( - OPTIMIZER, - exclude = config["nlp_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) MINLPTests.test_nlp_expr( - OPTIMIZER, - exclude = config["nlp_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) end @testset "NLP-CVX" begin + exclude = get(config, "nlpcvx_exclude", String[]) MINLPTests.test_nlp_cvx( - OPTIMIZER, - exclude = config["nlpcvx_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) MINLPTests.test_nlp_cvx_expr( - OPTIMIZER, - exclude = config["nlpcvx_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) end - if name != "Ipopt" + if config["mixed-integer"] + exclude = vcat(get(config, "nlpmi_exclude", String[]), ["006_010"]) @testset "NLP-MI" begin MINLPTests.test_nlp_mi( - OPTIMIZER, - exclude = config["nlpmi_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) MINLPTests.test_nlp_mi_expr( - OPTIMIZER, - exclude = config["nlpmi_exclude"], + OPTIMIZER; + exclude = exclude, termination_target = TERMINATION_TARGET, primal_target = PRIMAL_TARGET, - objective_tol = config["tol"], - primal_tol = config["tol"], - dual_tol = config["dual_tol"], + objective_tol = get(config, "tol", 1e-5), + primal_tol = get(config, "tol", 1e-5), + dual_tol = get(config, "dual_tol", 1e-5), ) end end end + +function test_uno_runtests() + optimizer = MOI.instantiate( + () -> AmplNLWriter.Optimizer(Uno_jll.amplexe, ["logger=SILENT"]); + with_cache_type = Float64, + with_bridge_type = Float64, + ) + MOI.Test.runtests( + optimizer, + MOI.Test.Config( + atol = 1e-4, + rtol = 1e-4, + optimal_status = MOI.LOCALLY_SOLVED, + infeasible_status = MOI.LOCALLY_INFEASIBLE, + exclude = Any[ + MOI.VariableBasisStatus, + MOI.ConstraintBasisStatus, + MOI.ObjectiveBound, + ], + ); + exclude = [ + # OTHER_LIMIT instead of LOCALLY_SOLVED + r"^test_conic_linear_VectorOfVariables_2$", + r"^test_nonlinear_expression_hs109$", + r"^test_quadratic_constraint_GreaterThan$", + r"^test_quadratic_constraint_LessThan$", + r"^test_solve_VariableIndex_ConstraintDual_MAX_SENSE$", + r"^test_solve_VariableIndex_ConstraintDual_MIN_SENSE$", + # OTHER_ERROR instead of LOCALLY_SOLVED + r"^test_linear_integration$", + r"^test_linear_transform$", + # OTHER_LIMIT instead of DUAL_INFEASIBLE + r"^test_solve_TerminationStatus_DUAL_INFEASIBLE$", + # OTHER_LIMIT instead of LOCALLY_INFEASIBLE + r"^test_conic_NormInfinityCone_INFEASIBLE$", + r"^test_conic_NormOneCone_INFEASIBLE$", + r"^test_conic_linear_INFEASIBLE$", + r"^test_conic_linear_INFEASIBLE_2$", + r"^test_linear_INFEASIBLE$", + r"^test_linear_INFEASIBLE_2$", + r"^test_solve_DualStatus_INFEASIBILITY_CERTIFICATE_", + # Uno does not support integrality + "Indicator", + r"[Ii]nteger", + "Semicontinuous", + "Semiinteger", + "SOS1", + "SOS2", + "ZeroOne", + r"^test_cpsat_", + # Existing MOI issues + r"^test_attribute_SolverVersion$", + r"^test_nonlinear_invalid$", + r"^test_basic_VectorNonlinearFunction_", + ], + ) + return +end + +test_uno_runtests()