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

  • Thread starter Thread starter davidfur
  • Start date Start date
  • Tags Tags
    Functions
AI Thread Summary
The discussion focuses on integrating an external header-only library for local minimization in a C++ project involving Particle and Group classes. The library's LBFGS minimization function requires a specific objective function signature, while the user's cost function and gradient calculations involve additional parameters. A suggested solution is to use a lambda expression to capture the necessary parameters and facilitate the interface between the library and the user's functions. Alternative methods, such as using function objects or functors, are also mentioned, but they may require more complexity compared to the straightforward lambda approach. Overall, the lambda expression is highlighted as an effective solution for this integration challenge.
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::cout << "lbfgs: sphere test completed successfully." << std::endl;
    } else {
        std::cout << "lbfgs: sphere test completed unsuccessfully." << std::endl;
    }
 
    arma::cout << "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 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 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 sysprog and davidfur
Thread 'Is this public key encryption?'
I've tried to intuit public key encryption but never quite managed. But this seems to wrap it up in a bow. This seems to be a very elegant way of transmitting a message publicly that only the sender and receiver can decipher. Is this how PKE works? No, it cant be. In the above case, the requester knows the target's "secret" key - because they have his ID, and therefore knows his birthdate.
I tried a web search "the loss of programming ", and found an article saying that all aspects of writing, developing, and testing software programs will one day all be handled through artificial intelligence. One must wonder then, who is responsible. WHO is responsible for any problems, bugs, deficiencies, or whatever malfunctions which the programs make their users endure? Things may work wrong however the "wrong" happens. AI needs to fix the problems for the users. Any way to...
Back
Top