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

  • Thread starter davidfur
  • Start date
  • Tags
    Functions
In summary, the author would like to implement a local minimization algorithm for a set of coordinates, however he is not sure how to do it using the provided library. He has found a library that does the job but it has a fixed way of doing things. He has written his own code which is based on two 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. He has found a library that implements the LBFGS minimization algorithm but it takes more arguments than he needs. He has found a way to do it without the lambda trick by capturing all the parameters by reference.
  • #1
davidfur
18
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
  • #2
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
  • #3
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!
 
  • #4
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

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

1. How do I include an external header-only library in my code?

To include an external header-only library in your code, you need to add the path to the library's header files in your code's include statements. For example, if the library is located in a folder called "library" within your project directory, you would add the following statement at the top of your code: #include "library/header_file.h".

2. How do I know which functions are available in the external header-only library?

The best way to know which functions are available in the external header-only library is to refer to the library's documentation or README file. These resources usually list all the available functions and their descriptions.

3. How do I use the functions defined in the external header-only library?

To use the functions defined in the external header-only library, you need to follow the same syntax as you would for any other function. First, include the library's header files in your code, then call the function using its name and any necessary arguments. For example, if the function is called calculateSum, you would use it as calculateSum(arg1, arg2).

4. Can I modify the functions defined in the external header-only library?

No, you cannot modify the functions defined in the external header-only library. These libraries are designed to be used as-is and any changes made to the code may result in unexpected behavior. If you need to modify the functionality of a function, it is best to create your own version of the function in your code.

5. Can I use an external header-only library in multiple files?

Yes, you can use an external header-only library in multiple files. As long as you include the library's header files in each file where you want to use the functions, you can access them and use them in your code.

Similar threads

Back
Top