Debugging NLopt :FORCED_STOP

Hello,
I have some issue when using NLopt.jl to perform constrained minimization. I have a function that I am trying to maximize, whose value depend from the solution of a system of ODE. However the optimizer suddenly terminate with return code :FORCED_STOP. By this I don’t mean that it stops immediately after the first attempt/step as in here, but it perform a certain amount of function calls (~ 100) and then stops.
I have to specify also that

  1. I am using a derivative-free method, in particular :GN_AGS or :LN_COBYLA to refine locally the solution
  2. I added a check on the function I am trying to maximize in the form of if sol.retcode != :Success do other stuff in order to avoid problems with the solution
  3. Weirdly the optimization fully works when changing some other parameters (unoptimized) of the system
  4. I am using a wrapper function nlfunc to have it in the form that NLopt.jl likes, and possibly compute the gradient with Autodiff.

Here is a sketch of the code

u0 = init_profile()
prob = ODEProblem(df!,u0,(0.0,T_horizon))

N = 4                        # or something in the same order
p_start = rand(N)     # or some more accurate choice 
opt = Opt(:GN_AGS, N)
opt.max_objective = nlfunc
opt.initial_step = 0.01*ones(N)
opt.maxtime = MAX_TIME

#constraints
opt.lower_bounds = zeros(N)
opt.upper_bounds = ones(N)
inequality_constraint!(opt,example_constraint,1e-4)

V,p_next,ret_code = optimize(opt, p_start)

with the auxiliary functions

function df!(du,u,p,t)
     du .= your_ode(p,some_other_parameters)
end
function maximize_me(p)
    sol_p = DifferentialEquations.solve(
        prob,someAlg(),p=p,saveat=save_times)

    if sol_p.retcode != :Success
        return 0.0
    else
        return value_depending_on_sol(sol_p,p)
    end
end

function nlfunc(p,grad)   #wrapper for NLopt with AutoDiff for grandient
    if length(grad) != 0
        println("---> computed gradient")
        ForwardDiff.gradient!(grad,maximize_me, p)
    end
    ret = maximize_me(p)
    return ret
end

Now the question is: How do you usually debug such problems with NLopt.jl? From the documentation of NLopt it looks like the error :FORCED_STOP arise when there is an error or an exception in the function one tries to minimize/maximize. However julia doesn’t throws any warning or such, hence I don’t know how to dig deeper.

Thanks in advance

Log the points at which it is evaluating the function by adding a println(p) to your nlfunc. That may help.

It’s hard to offer more advice without a minimal working example (what is value_depending_on_sol, for example).

1 Like

You might also consider a try/catch block in your function that catches the error and prints it:

function maximize_me(p)
  try 
    return sum(log, p)
  catch er
    println("An error happened! It was $er")
    throw(er) # NLopt will probably swallow this one, which is why the printing is helpful.
  end
end

or something of that sort. I’m sure there’s a more elegant way to show the error with the whole stacktrace and stuff, but something like that is typically good enough for print debugging in the settings where I’ve had to use it.

Partial answer to both @cgeoga and @odow
I already tried adding println here and there to debug everything.
However @cgeoga advice showed something quite surprising: I did exactly as you suggested and nothing happened. However I tried also to do the same thing inside the function nlfunc (wrapper for NLopt) and what happen is that:

  • the optimization terminates immediately, at the first step (while usually kept running for a while)
  • nothing gets printed!!! (of course with the println before the throw

That is quite suprising to me.
I don’t know how NLopt.jl works internally, when the C++ library gets called, but maybe it is related to how/when the julia objects are passed to it.

I tried also something else, like initializing an empty array and pushing the error message into it but no luck. Actually the optimization terminates even without the throw(er) command. It seems the try catch statement already breaks it.

This doesn’t solve the problem, but at least gives some advice on where to look at

I already tried adding println here and there to debug everything.

So what did it show? What was p and what was ret?

reasonable values,
In my example p is a Vector of length 4 with values between 0 and 1. And that was what I saw by printing it in various stages.

moving on I worked some more time on it and find the solution to my problem, but nothing to the general usage of NLopt.
In my case there was an error in

function df!(du,u,p,t)
     du .= your_ode(p,some_other_parameters)
end

As I anticipated, there was an error somewhere, I just needed to find where. Anyhow the error was not in the DifferentialEquations.solve part, meaning that for some choices of the parameter p the function your_ode(p,some_other_parameters) actually produced an error (in Julia). So there was no solution to come out and hence no way for me to detect it with if sol_p.retcode != :Success.

What makes this a bit difficult is that, even if there was a Julia error (trying to select x[nothing] where x is a Vector), there was no way to know this, since NLopt swallows everything inside.

Do you know if using a Julia logger, a verbose mode, or some other shenanigans on NLopt, could makes it a bit more descriptive on the problem behind :FORCED_STOP? It’s a bit tricky otherwise

(btw thanks for help, in particular for the usage of try/catch that eventually lead me to the answer)

2 Likes

Something like:

scalarDev = try
       f(x)
catch e
    	bt = catch_backtrace()
    	showerror(stdout, e, bt)
        @info "Guess vector:  $(round.(x, digits = 3))"
        rethrow(e)
end        
2 Likes