Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support force stop #24

Open
awvwgk opened this issue Jul 26, 2022 · 9 comments
Open

Support force stop #24

awvwgk opened this issue Jul 26, 2022 · 9 comments
Labels
enhancement New feature or request

Comments

@awvwgk
Copy link
Member

awvwgk commented Jul 26, 2022

We have bindings for the force stop procedure, however there is no way to use them easily from the callback as we need access to the handle to make use of it.

Having the function wrapper store the handle might be an option, however this makes it more difficult to reuse a function in different optimizers.

@awvwgk awvwgk added the enhancement New feature or request label Jul 26, 2022
@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 29, 2022

Isn't this a problem of the C interface too (see forced termination)? The only way you can pass a nlopt_opt instance to the callback is either you have a global opt variable or you pass it using the void *f_data dummy argument?

In Ceres, the FirstOrderFunction uses a boolean to distinguish between success and failure.

What you could do here is introduce a new set of callback interfaces which flip a success flag. Then you can use this logical flag to force stop in the adaptor/wrapper function. The wrapper function would obtain access to the handle through host association. This means the adaptor/wrapper would need to be an internal procedure of either set_min_objective or optimize, depending on how you organize things. I've used the "trick" - Fortran host association in internal functions - before, to use qsort for indirect sorting.

@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 29, 2022

Maybe using a derived type as the result could reduce interface verbosity:

function nlopt_func_interface(x, gradient, func_data) result(f)
      import :: c_int, c_double, c_ptr
      implicit none
      real(c_double), intent(in) :: x(:)
      real(c_double), intent(inout), optional :: gradient(:)
      class(*), intent(in), optional :: func_data
      real(c_double) :: f
    end function
subroutine firstorder_callback(x,result,func_data)
   real(c_double), intent(in) :: x(:)
   type(result_type), intent(inout) :: result ! intent(inout) needed to keep gradient array intact
   class(*), intent(in), optional :: func_data

   result%success = .true. ! could be assumed by default

   result%cost = ...
   if (result%expects_grad) then ! or, if(has_grad(result)), or if (associated(result%gradient))
     result%gradient = ...  ! grad
   end if
   
end subroutine

@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 29, 2022

In the C++ wrapper, they assume users will throw an exception to force a stop. They then pass the opt object as the f_data: https://github.com/stevengj/nlopt/blob/42c43f3938b201042798c3925935a914b3049ed8/src/api/nlopt-in.hpp#L126

Seeing this solution, the lowest hanging fruit would be to provide an extended nlopt_func_interface, i.e. nlopt_func_interface_with_stop, with an optional force_stop flag. Or you add it to the existing interface (which is a breaking change).

@awvwgk
Copy link
Member Author

awvwgk commented Jul 29, 2022

Passing through the object handle as void* would be the most straightforward implementation. However, this detail should be hidden to not clutter the interface.

In the current nlopt_wrap implementation we already have a problem with object lifetimes, mainly due to the fact that the solver and the objective function are separate objects. Having the handle as part of the objective function would defeat the point of separating the problem from the solver.

@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 29, 2022

Having the handle as part of the objective function would defeat the point of separating the problem from the solver.

Indeed. If you can roll out an interface with a boolean, it can be reused for the Ceres first-order function solver. Same problem definition, but with two different solver libraries.

@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 29, 2022

NAG uses the following interface in e04kdf

! NAG E04KDF (bounds_mod_deriv_comp)
!
Subroutine funct (  iflag, n, xc, fc, gc, iw, liw, w, lw)
Integer, Intent (In)    ::  n, liw, lw        ! Array sizes
Integer, Intent (Inout) ::  iflag             ! Will have been set to 1 or 2 on entry, set negative to terminate
Integer, Intent (Inout) :: iw(liw)            ! Integer array (User workspace)
Real (Kind=nag_wp), Intent (In) ::  xc(n)     ! Real array, point x at which to evaluate
Real (Kind=nag_wp), Intent (Inout)  :: w(lw)  ! Real array (User Workspace)
Real (Kind=nag_wp), Intent (Out)    :: fc     ! Objective function F at the current point 
Real (Kind=nag_wp), Intent (Out)    :: gc(n)  ! Gradient array

The meaning of iflag is:

On entry: will have been set to 1 or 2. The value 1 indicates that only the first derivatives of F need be supplied, and the value 2 indicates that both F itself and its first derivatives must be calculated.
On exit: if it is not possible to evaluate F or its first derivatives at the point given in xc (or if it is wished to stop the calculations for any other reason) you should reset iflag to a negative number and return control to e04kdf.

Unfortunately, this specification is at odds with the NLopt expectations (either F or both F and grad(F)). Not that many NAG users are seeking an open-source alternative...

Edit: Some of their newer routines, like e04kff (handle_​solve_​bounds_​foas) also accept a void pointer for passing additional parameters:

Subroutine objfun ( nvar, x, fx, inform, iuser, ruser, cpuser)
Integer, Intent (In)	::	nvar
Integer, Intent (Inout)	::	inform, iuser(*)
Real (Kind=nag_wp), Intent (In)	::	x(nvar)
Real (Kind=nag_wp), Intent (Inout)	::	ruser(*)
Real (Kind=nag_wp), Intent (Out)	::	fx
Type (c_ptr), Intent (In)	::	cpuser

(In this routine, the gradient callback is passed separately; inform replaces iflag, again it must be set negative to terminate)

@awvwgk
Copy link
Member Author

awvwgk commented Jul 30, 2022

you should reset iflag to a negative number and return control to e04kdf.

This is similar to the minpack callback which has an iflag argument to communicate an exceptional state.

@ivan-pi
Copy link
Collaborator

ivan-pi commented Jul 30, 2022

In many cases NAG has two sets of routines (and accompanying interfaces): "easy" routines, with no option to force stop or recover from errors, and expert routines for edge cases. An easy routine can help users adopt your library. They can upgrade to the expert interface when they need it. It does however increase the # of LOC in the library.

@awvwgk
Copy link
Member Author

awvwgk commented Jul 30, 2022

Implementing the easy routines would be done by wrapping the expert routines internally, which should be more or less fine in term of required code. Having a different callback interface however means another adaption layer in between to match signatures, which might be undesirable, but I guess additional function calls themselves are not that expensive, depending on the objective function to evaluate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants