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

Alternative to associating objective functions #13

Open
awvwgk opened this issue Jan 29, 2022 · 2 comments
Open

Alternative to associating objective functions #13

awvwgk opened this issue Jan 29, 2022 · 2 comments
Labels
enhancement New feature or request

Comments

@awvwgk
Copy link
Member

awvwgk commented Jan 29, 2022

Currently we implement objective functions by

  1. creating callback wrappers with bind(c) and pass those as procedure pointers to NLopt
  2. safe the actual user-defined objective function as data pointer (void*)
  3. resolve the user-defined objective function from our callback wrapper
  4. invoke the actual objective function

The drawback of this approach is that we only have a pointer to the objective function and the function object must at least exist until we call the optimize method. Therefore, using the following (intuitive) approach will fail:

call opt%set_min_objective(nlopt_func(myfunc))

call opt%add_inequality_constraint(nlopt_func(myconstraint, d1), 1.0e-8_wp)
call opt%add_inequality_constraint(nlopt_func(myconstraint, d2), 1.0e-8_wp)

call opt%set_xtol_rel(xtol)

x = [1.234_wp, 5.678_wp]
call opt%optimize(x, minf, stat)

Since the function objects contain only a procedure pointer and a class polymorphic pointer, which don't own any data, we could in principle copy those objects into our nlopt_opt instance on the Fortran side and ensure they have the same lifetime as the instance itself.

This would be done in one of the hooks to register an objective function like

nlopt-f/src/nlopt_wrap.f90

Lines 281 to 290 in 696b0e2

subroutine set_min_objective(self, f, stat)
class(nlopt_opt), intent(inout) :: self
type(nlopt_func), intent(in), target :: f
integer(ik), intent(out), optional :: stat
integer(nlopt_result) :: istat
istat = nlopt_set_min_objective(self%handle, c_funloc(wrap_func), c_loc(f))
call handle_result(stat, istat)
end subroutine set_min_objective

However, any mechanism to own the function object in the nlopt_opt instance must ensure that the address of the function object is not changed by any operation on the wrapper, e.g. adding another objective function and resizing an array of function objects.

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

ivan-pi commented Feb 6, 2022

I've been playing around with the idea of placing the function and constraints inside of a nonlinear optimization problem derived type. The interface becomes closer to the Julia NLopt.jl wrapper. Here's a sample:

program nlopt_alternative_demo

   use example_funcs
   use nlopt_alternative
   implicit none

   type(nlopt_prob(n = 2)) :: prob

   type :: constraint_data
      real(wp) :: a, b
   end type

   type(nlopt_func) :: cnstr(2)
   type(constraint_data), target :: d1, d2

   call create(prob, "LD_MMA")  ! initializes with default settings

   prob%lower_bounds(2) = 0.0_wp
   prob%xtol_rel = 1.e-4_wp

   prob%min_objective = nlopt_func(myfunc)

   d1 = constraint_data(2.0_wp, 0.0_wp)
   d2 = constraint_data(-1.0_wp, 1.0_wp)

   cnstr(1) = nlopt_func(myconstraint, d1)
   cnstr(2) = nlopt_func(myconstraint, d2)

   call add_inequality_constraints(opt, cnstr)

   x = [1.234_wp, 5.678_wp]

   call optimize(prob, x)

   print *, "Found minimum ", prob%optf, "at ", x
   print *, "Number of function evaluations ", prob%numevals

end program

A few other choices I've been playing with:

  • use PDT for the problem dimensionality
  • maintain copies of all the settings on Fortran side (eliminates getters and setters)
  • pass constraints packed as an array

The actual settings get passed through the C-API directly within the call to optimize.

@awvwgk
Copy link
Member Author

awvwgk commented Feb 6, 2022

One problem is the assignment(=) which will require to use nlopt_copy to duplicate the handle and all the associated data, like the procedure/data pointer. The solution to actually defer all this to the optimize call is useful, however in case of an error, at which point do you become aware about passing an incorrect value or missing an upper bound?

I like the idea of having a nlopt_problem object/module, this could be built on top of the existing wrapper, especially if the creation of the handle and the setup is completely deferred to the optimize call. I think we can support something along this lines in nlopt-f.

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