From 8cc7aee468d07152f1c0b8f464294bcd39b2f6de Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 19 Aug 2021 17:52:49 +1200 Subject: [PATCH 1/3] [Test] Add test for NaN in NLP callbacks --- src/Test/test_nonlinear.jl | 72 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/Test/test_nonlinear.jl b/src/Test/test_nonlinear.jl index b0dbd80d20..f336ae94ee 100644 --- a/src/Test/test_nonlinear.jl +++ b/src/Test/test_nonlinear.jl @@ -850,3 +850,75 @@ function test_nonlinear_Feasibility_internal(::MOI.ModelLike, ::Config) @test H == [2.2] return end + +""" + InvalidEvaluator() + +An AbstractNLPEvaluator for the problem: +``` +min sqrt(x) + x <= -1 +``` +""" +struct InvalidEvaluator <: MOI.AbstractNLPEvaluator end + +function MOI.initialize(d::InvalidEvaluator, requested_features::Vector{Symbol}) + for feat in requested_features + @assert feat in MOI.features_available(d) + end + return +end + +function MOI.features_available(::InvalidEvaluator) + return [:Grad, :ExprGraph] +end + +MOI.objective_expr(::InvalidEvaluator) = :(0 * x[$(MOI.VariableIndex(1))] / 0) + +MOI.constraint_expr(::InvalidEvaluator, i::Int) = :() + +MOI.eval_objective(::InvalidEvaluator, x) = NaN + +MOI.eval_constraint(::InvalidEvaluator, g, x) = nothing + +function MOI.eval_objective_gradient(::InvalidEvaluator, grad_f, x) + grad_f[1] = NaN + return +end + +""" + test_nonlinear_invalid(model::MOI.ModelLike, config::Config) + +Test that a nonlinear model returns the TerminationStatus `INVALID_MODEL` if a +NaN is detected in a function evaluation. +""" +function test_nonlinear_invalid(model::MOI.ModelLike, config::Config) + @requires MOI.supports(model, MOI.NLPBlock()) + @requires _supports(config, MOI.optimize!) + MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set( + model, + MOI.NLPBlock(), + MOI.NLPBlockData(MOI.NLPBoundsPair[], InvalidEvaluator(), true), + ) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INVALID_MODEL + return +end + +function setup_test( + ::typeof(test_nonlinear_invalid), + model::MOIU.MockOptimizer, + ::Config, +) + MOI.Utilities.set_mock_optimize!( + model, + (mock) -> MOI.Utilities.mock_optimize!( + mock, + MOI.INVALID_MODEL, + [NaN], + ), + ) + return +end From 2252cc170a891b936b6891f25cef1441d392f9fa Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 19 Aug 2021 17:58:35 +1200 Subject: [PATCH 2/3] Update test_nonlinear.jl --- src/Test/test_nonlinear.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Test/test_nonlinear.jl b/src/Test/test_nonlinear.jl index f336ae94ee..fe3fe0a043 100644 --- a/src/Test/test_nonlinear.jl +++ b/src/Test/test_nonlinear.jl @@ -914,11 +914,7 @@ function setup_test( ) MOI.Utilities.set_mock_optimize!( model, - (mock) -> MOI.Utilities.mock_optimize!( - mock, - MOI.INVALID_MODEL, - [NaN], - ), + mock -> MOI.Utilities.mock_optimize!(mock, MOI.INVALID_MODEL, [NaN]), ) return end From b6ce713d8eaad1e65846c038c2d06067c6957110 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 20 Aug 2021 09:58:11 +1200 Subject: [PATCH 3/3] Update test_nonlinear.jl --- src/Test/test_nonlinear.jl | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Test/test_nonlinear.jl b/src/Test/test_nonlinear.jl index fe3fe0a043..f45066f6c5 100644 --- a/src/Test/test_nonlinear.jl +++ b/src/Test/test_nonlinear.jl @@ -854,11 +854,10 @@ end """ InvalidEvaluator() -An AbstractNLPEvaluator for the problem: -``` -min sqrt(x) - x <= -1 -``` +An AbstractNLPEvaluator for the problem: `min NaN`, where `eval_objective` +returns a `NaN`. + +Solvers should return a `TerminationStatus` of `MOI.INVALID_MODEL`. """ struct InvalidEvaluator <: MOI.AbstractNLPEvaluator end @@ -873,19 +872,37 @@ function MOI.features_available(::InvalidEvaluator) return [:Grad, :ExprGraph] end -MOI.objective_expr(::InvalidEvaluator) = :(0 * x[$(MOI.VariableIndex(1))] / 0) - -MOI.constraint_expr(::InvalidEvaluator, i::Int) = :() +MOI.objective_expr(::InvalidEvaluator) = :(NaN) MOI.eval_objective(::InvalidEvaluator, x) = NaN -MOI.eval_constraint(::InvalidEvaluator, g, x) = nothing - function MOI.eval_objective_gradient(::InvalidEvaluator, grad_f, x) grad_f[1] = NaN return end +""" + test_nonlinear_InvalidEvaluator_internal(::MOI.ModelLike, ::Config) + +A test for the correctness of the InvalidEvaluator 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_InvalidEvaluator_internal(::MOI.ModelLike, ::Config) + d = InvalidEvaluator() + @test MOI.objective_expr(d) == :(NaN) + MOI.initialize(d, [:Grad, :ExprGraph]) + x = [1.5] + # f(x) + @test isnan(MOI.eval_objective(d, x)) + # f'(x) + ∇f = fill(NaN, length(x)) + MOI.eval_objective_gradient(d, ∇f, x) + @test isnan(∇f[1]) + return +end + """ test_nonlinear_invalid(model::MOI.ModelLike, config::Config)