How to use functions defined in an external header-only library?

  • Context:
  • Thread starter Thread starter davidfur
  • Start date Start date
  • Tags Tags
    Functions
Click For Summary

Discussion Overview

The discussion revolves around integrating a local minimization algorithm using an external header-only library in a C++ program that involves classes for Particle and Group. Participants explore how to adapt their existing cost function and gradient evaluation methods to work with the library's requirements for function signatures.

Discussion Character

  • Technical explanation
  • Exploratory
  • Debate/contested

Main Points Raised

  • One participant describes their implementation of classes for Particle and Group, detailing how they want to use a local minimization algorithm with an external library.
  • The library's LBFGS function requires a specific function signature for the objective function, which complicates integration with the participant's existing methods that have additional parameters.
  • Another participant suggests using a lambda expression to capture necessary parameters and call the library's function, providing an example of how to implement this.
  • A third participant expresses surprise at the effectiveness of lambda expressions for this purpose and inquires about alternative methods for achieving the same goal.
  • A fourth participant discusses the concept of function objects or functors as an alternative to lambdas, explaining their use in templated libraries and the potential complexity involved in using them compared to lambdas.

Areas of Agreement / Disagreement

Participants generally agree on the utility of lambda expressions for passing member functions to the library's function. However, there is no consensus on the best alternative methods, as the discussion includes differing opinions on the use of functors and their complexity.

Contextual Notes

Some participants note that the library's method only supports functionals directly, which may limit the use of functors without additional wrappers. There are also considerations regarding the safety of capturing parameters by reference or value in lambda expressions.

Who May Find This Useful

Readers interested in C++ programming, particularly those working with optimization algorithms, function signatures, and integrating external libraries into their code may find this discussion relevant.

davidfur
Messages
18
Reaction score
2
Hey guys,
I have written a C++ code which is based on two main classes: Particle and Group. Each Group contains a set of Particle(s), each Particle is defined by a set of coordinates, and has an associated energy and force (the energy/force evaluation is done by calling an external program). I would like to implement a local minimization algorithm that takes the coordinates, energy and gradient of a given Particle, and outputs a new set of coordinates which correspond to the zero gradient solution (i.e. a local minimum).

I have found this library, which looks pretty neat, however it has a somewhat fixed way of how the objective function should be defined. In short, the LBFGS minimization algorithm from that library is called with:
C++:
bool bfgs(arma::vec& init_out_vals, std::function<double (const arma::vec& vals_inp, arma::vec* grad_out, void* opt_data)> opt_objfn, void* opt_data);
and an example usage is the following:

C++:
#include "optim.hpp"

double sphere_fn(const arma::vec& vals_inp, arma::vec* grad_out, void* opt_data)
{
    double obj_val = arma::dot(vals_inp,vals_inp);
    //
    if (grad_out) {
        *grad_out = 2.0*vals_inp;
    }
    //
    return obj_val;
}int main()
{
    //
    // sphere function

    const int test_dim = 5;

    arma::vec x = arma::ones(test_dim,1); // initial values (1,1,...,1)

    bool success = optim::lbfgs(x,sphere_fn,nullptr);

    if (success) {
        std::count << "lbfgs: sphere test completed successfully." << std::endl;
    } else {
        std::count << "lbfgs: sphere test completed unsuccessfully." << std::endl;
    }
 
    arma::count << "lbfgs: solution to sphere test:\n" << x << arma::endl;
    return 0;
}

All of the above is nice and works smoothly, HOWEVER, in my code the objective function, gradients and coordinates are implemented a bit differently, and I'm not sure what is the easiest way of "interfacing" the two (i.e. using the above library to do what it's supposed to do with the Particle(s) in my code).

This is the definition of the cost function I want to be minimized with the library.

C++:
double Par::eval_cost(const arma::vec &active_params, int cycle, int iter, int parid) {... evaluate cost function based on the coordinates specified by active_params

return cost

};

So, this cost function takes more arguments which are needed to calculate the cost.

A separate member function calculates the derivatives of the cost function at the coordinates:
C++:
arma::vec Par::eval_numgrad(arma::vec active_params, int cycle, int iter, int parid) {

  evaluate numerical derivatives with respect to active_params by calling eval_cost for different values of active_params (i.e. central finite-difference)
 
  return numgrad;
};

Again, the eval_numgrad function takes the same 4 arguments to evaluate and return the numerical derivatives vector.

So, my question is with the above implementation of eval_cost and eval_numgrad, how should one go about using the library LBFGS function?
I'd be grateful for some guidelines, since I'm not very familiar with passing member functions to non-member functions, etc.

Thanks!
 
  • Like
Likes   Reactions: sysprog
Technology news on Phys.org
Since the signature uses std::function you should be able to call directly with a lambda expression that captures the instance and parameters needed. One example could be something like:

C++:
// establish parameters and initial value
int cycle = ...
int iter = ...
int parid = ...
arma::vec x = ...

// optimize using eval_cost and eval_numgrad, capturing all parameters by reference
bool success = optim::lbfgs(x,[&](const arma::vec& vals_inp, arma::vec* grad_out, void* opt_data) {
  if (grad_out) {
    *grad_out = eval_numgrad(vals_inp, cycle, iter, parid);
  }
  return eval_cost(vals_inp, cycle, iter, parid);
}, nullptr);

This assumes arma::vec has assignment operator and that you change signature of your eval_numgrad method so first parameter is a const reference (and not just a reference). Perhaps better, change your eval_numgrad to include a output vector reference as first argument and then set the components of that vector in your method. The call would then instead look like

C++:
if (grad_out) {
    eval_numgrad(*grad_out, val_inp, cycle, iter, parid);
}

Notice, that in the example above all the parameters for the lambda expression are implicitely captures by reference (the [&] part). This assumes that the call to optum::lbgfs never uses the supplied function after it returns. i.e. that the function is not stored by the library and called again later after the expression has gone out of scope. In general there are many ways to do this depending on the context. For instance, a more safe capture is default capture by value (i.e. [=]) which also is performing well if there are no large data structures copied by value (as in your case).
 
  • Like
Likes   Reactions: sysprog and jim mcnamara
Wow. This works like magic! I've never heard about "lambda" before! this is exactly what I needed to overcome the "barrier" in transferring member functions to general functions.

Though, I wonder how else this could be done without the lambda trick...

Thanks Filip!
 
davidfur said:
I wonder how else this could be done without the lambda trick

In general, a very common pattern that predates lambdas (functionals) is what is called function objects or functors, especially with templated libraries like STL that uses them heavily. These have no additional "magic" under the hood compare to lambdas which makes them simpler to understand but also quickly more involved to use once you want to have parameterized functors (lambdas on the other hand allow for some very concise code even in advances scenarios).

However, using a functor to "pass in a function" to a (library) method requires that the method supports that notion, either via templating or via a virtual method, and as far as I can see by a quick glance at the github repository for the optim library you are using, the lbfgs method only seems to support functionals directly.

Technically you can of course write a small template wrapper that takes a functor and wraps it in a lambda, but that would only make sense if you already had a large set of functors that you want to call with. It would probably look something like shown below where the Functor instance then must have an () operator declared with the same signature as the functional has (or you could skip opt_data if that is not used in your case).

C++:
template <typename Functor>
bool lbfgs(arma::vec& init_out_vals, Functor opt_objfn, void* opt_data) {
  return optum::lbfgs(init_out_vals, [=](const arma::vec& vals_inp, arma::vec* grad_out, void* opt_data) {
    return opt_objfn(vals_inp, grad_out, opt_data);
  });
}
 
  • Like
Likes   Reactions: sysprog and davidfur

Similar threads

Replies
6
Views
2K
  • · Replies 11 ·
Replies
11
Views
2K
  • · Replies 7 ·
Replies
7
Views
2K
  • · Replies 1 ·
Replies
1
Views
2K
  • · Replies 8 ·
Replies
8
Views
6K
  • · Replies 1 ·
Replies
1
Views
6K
  • · Replies 6 ·
Replies
6
Views
3K
  • · Replies 2 ·
Replies
2
Views
2K
  • · Replies 2 ·
Replies
2
Views
2K
Replies
1
Views
5K