Modern Fortran interface for NLopt

Not to undermine Sebastian’s large effort, however, I would like to mention that I also attempted to create a modern Fortran interface back in 2019. I even tried to submit it as pull request ([WIP] A Modern Fortran Interface by ivan-pi · Pull Request #233 · stevengj/nlopt · GitHub) to the original NLopt repository. Preceding my own attempt, GitHub member aitap described how to wrap NLopt in a blog post (Wrapping C opaque pointers in Fortran 2003 in type-strict way).

The factors blocking my PR at the time were CMake integration in the upstream project and also some struggles with memory leaks and interfaces in the object-oriented Fortran wrapper (Actual and dummy procedure argument mismatch - Error 7062 - Intel Communities).

Since some NLopt users already depend upon the “old” (standard-violating…) interface, I think it would be difficult to have a modern interface co-exist in the original NLopt development repository. I also had the feeling the maintainers (and creators) of NLopt aren’t particularly interested in modern Fortran as a contemporary programming language.

Hence, I ultimately closed my PR and switched to using @awvwgk’s wrappers instead of my own (incomplete) version. AFAICT the nlopt-f interface is completely type-safe and agnostic of the fact the underlying library is in C. It only assumes that you have nlopt installed somewhere.

My takeaway from this whole experience is that fragmentation of the community can really hinder progress. Another lesson I learned is the importance of “finishing” a project to a level where others can use it safely.


I would add a note for future Fortran optimization libraries (cc @zaikunzhang), if they could provide a nlopt-f callback compatible interface, it would make switching between optimization libraries much easier for consumers. The current interface expected in nlopt-f is:

    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

If you already have an optimization library with different callback conventions for the objective function and parameters, you can employ the adaptor pattern to offer consumers a type-safe interface. The same pattern is in fact used in nlopt-f to interface with the original library in C.

For even wider consumption potentially through NLopt itself (if the maintainers agree upon it), your optimization algorithm (as a Fortran subprogram) would need to export an interface similar to:

nlopt_result cobyla_minimize(unsigned n, nlopt_func f, void *f_data,
                             unsigned m, nlopt_constraint *fc,
                             unsigned p, nlopt_constraint *h,
                             const double *lb, const double *ub, /* bounds */
                             double *x, /* in: initial guess, out: minimizer */
                             double *minf,
                             nlopt_stopping *stop,
                             const double *dx);

This however means you agree to implement all the possible stopping criteria expected by the NLopt API (see nlopt_stopping or nlopt_result), as well as some further conditions upon the constraints (e.g. NLopt expects the objective function will never be evaluated outside of the bounds) among other things.

6 Likes