NLopt: Finding errors in objective function

When the objective function throws an exception, NLopt catches it and halts the optimization gracefully.
The NLopt run produces no indication that anything went wrong, except for the FORCED_STOP return value.
How can I find the source of the exception in such cases?

The case I have in mind has an objective function that runs just fine until the optimizer chooses a particular combination of parameters that triggers the exception.
One option would be to solve the objective function for a large number of random inputs, but that could be expensive. I am hoping for a way of reporting exceptions as the occur during the optimization.

Is it possible to post a MWE? Without that, it’s hard to give advice to something that sounds like a general problem.

This might be helpful
https://www.google.com/url?sa=t&source=web&rct=j&url=https://nlopt.readthedocs.io/en/latest/NLopt_Reference/%23forced-termination&ved=2ahUKEwiruu6Zq8_oAhU8TxUIHck7DeQQygQwAHoECAUQBg&usg=AOvVaw3aB7ZheUaueg-LJTTgOvOf&cshid=1586022834848

My understanding is that this section of the docs describes that the user can throw an exception at any point in the code and use the referenced function to provide additional information about the exception.
My problem is that I don’t know where the exception occurs.

I have not tried the debugger. One reason is that the exceptions tend to occur hours into a run (which happens on a cluster, not interactive).
My experience with the debugger is that it tends to be quite slow. I’m not sure that this is feasible in my case, but I could try.
Still, I am hoping for a way to bypass the “graceful” exception handling; or even better: handle the exception gracefully, but without supressing the error message.
Thank you.

It really is a general problem, but here is a MWE:

# test.jl
using NLopt

function f(x, y) 
    println("Input  $x")
    if x[1] > 0.5
        rVal = sum(x .^ 2)
        println("Returning  $rVal")
        return rVal
    else
        println("Error branch")
        error("This is not reported")
    end
end

optS = NLopt.Opt(:LN_SBPLX, 2);
optS.min_objective = f;

f([1.0, 1.0])

fVal, soln, exitFlag = NLopt.optimize(optS, [1.0, 1.0])

include("test.jl" yields

Input  [1.0, 1.0]
Returning  2.0
Input  [1.0, 1.0]
Returning  2.0
Input  [2.0, 1.0]
Returning  5.0
Input  [1.0, 2.0]
Returning  5.0
Input  [2.0, 0.0]
Returning  4.0
Input  [1.0, 0.0]
Returning  1.0
Input  [0.5, -0.5]
Error branch
(1.0, [1.0, 0.0], :FORCED_STOP)

Note that the error message (“This is not reported”) is suppressed. The question is how to find the location that generates the exception in the objective function.

Thank you.

You can do something like

function _f(x,y)
    try f(x,y)
    catch e
    	bt = catch_backtrace()
    	showerror(stdout, e, bt)
    	rethrow(e)
    end
end
julia> fVal, soln, exitFlag = NLopt.optimize(optS, [1.0, 1.0])
Input  [1.0, 1.0]
Returning  2.0
Input  [2.0, 1.0]
Returning  5.0
Input  [1.0, 2.0]
Returning  5.0
Input  [2.0, 0.0]
Returning  4.0
Input  [1.0, 0.0]
Returning  1.0
Input  [0.5, -0.5]
Error branch
This is not reported
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] f(::Array{Float64,1}, ::Array{Float64,1}) at ./REPL[3]:9
 [3] _f(::Array{Float64,1}, ::Array{Float64,1}) at ./REPL[8]:2
 [4] nlopt_callback_wrapper(::UInt32, ::Ptr{Float64}, ::Ptr{Float64}, ::Ptr{Nothing}) at /home/kc/.julia/packages/NLopt/eqN9a/src/NLopt.jl:413
 [5] optimize! at /home/kc/.julia/packages/NLopt/eqN9a/src/NLopt.jl:604 [inlined]
 [6] optimize(::Opt, ::Array{Float64,1}) at /home/kc/.julia/packages/NLopt/eqN9a/src/NLopt.jl:611
 [7] top-level scope at REPL[19]:1
 [8] eval(::Module, ::Any) at ./boot.jl:331
 [9] eval_user_input(::Any, ::REPL.REPLBackend) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:86
 [10] macro expansion at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:118 [inlined]
 [11] (::REPL.var"#26#27"{REPL.REPLBackend})() at ./task.jl:358(1.0, [1.0, 0.0], :FORCED_STOP)
2 Likes

Thank you. I think this is what I will try.

I was hoping to avoid it because of the potential performance penalty of the try / catch (though I don’t know whether this will be significant; something I will investigate).