resibots / limbo

A lightweight framework for Gaussian processes and Bayesian optimization of black-box functions (C++11)
http://www.resibots.eu/limbo
Other
239 stars 51 forks source link

Support for muliple instances in the same process #303

Closed portaloffreedom closed 3 years ago

portaloffreedom commented 4 years ago

I'm currently using the limbo library to run Bayesian Optimizations on robots in a simulation. The robots require different parameters and parameters dimensions from each other. I've noticed that in this library there are a lot of static functions and static variables used when configuring parameters.

Is it possible to improve the library so that multiple Bayesian Optimizer instances can be configured separately and run inside the same process safely?

costashatz commented 4 years ago

@portaloffreedom thanks for using our library!

Just to understand better: do you need to run multiple Bayesian optimizations in parallel? Or multiple robot simulations in parallel?

The latter should be possible already. If it is the former, can you please describe your use case in more detail? The BO parameters that are static can be shared between difference threads with no issue. And you can have different sets of parameters: e.g., one set of parameters for the hexapod robots and one set of parameters for the humanoid robots (you just need to create two different Params structs).

dmikushin commented 4 years ago

Hi, here is how static definitions fail the link step of our application, when I try to split a single GP source file into two:

[ 64%] Linking CXX shared library libgp.dylib
duplicate symbol 'limbo::tools::par::init()' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::tools::random_lhs(int, int)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::tools::random_vector_unbounded(int)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::tools::random_vector_bounded(int)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::tools::make_vector(double)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::opt::no_grad(double)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::tools::random_vector(int, bool)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::opt::fun(std::pair<double, boost::optional<Eigen::Matrix<double, -1, 1, 0, -1, 1> > > const&)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o
duplicate symbol 'limbo::opt::grad(std::pair<double, boost::optional<Eigen::Matrix<double, -1, 1, 0, -1, 1> > > const&)' in:
    CMakeFiles/gp.dir/src/limbo.cpp.o
    CMakeFiles/gp.dir/src/limbo_gp.cpp.o

This is on master branch, the most recent commit as of now. The same problem can be easily replicated by distributing the content of src/tutorials/gp.cpp between two source files.

costashatz commented 4 years ago

The same problem can be easily replicated by distributing the content of src/tutorials/gp.cpp between two source files.

How can you distribute the content of src/tutorial/gp.cpp in two source files? Can you give me your code?

dmikushin commented 4 years ago

Thanks for quick reply! Please see the test case below.

gp.h:

#include <fstream>
#include <limbo/kernel/exp.hpp>
#include <limbo/kernel/squared_exp_ard.hpp>
#include <limbo/mean/data.hpp>
#include <limbo/model/gp.hpp>
#include <limbo/model/gp/kernel_lf_opt.hpp>
#include <limbo/tools.hpp>
#include <limbo/tools/macros.hpp>

#include <limbo/serialize/text_archive.hpp>

// this tutorials shows how to use a Gaussian process for regression

struct Params {
    struct kernel_exp {
        BO_PARAM(double, sigma_sq, 1.0);
        BO_PARAM(double, l, 0.2);
    };
    struct kernel : public limbo::defaults::kernel {
    };
    struct kernel_squared_exp_ard : public limbo::defaults::kernel_squared_exp_ard {
    };
    struct opt_rprop : public limbo::defaults::opt_rprop {
    };
};

// the type of the GP
using Kernel_t = limbo::kernel::Exp<Params>;
using Mean_t = limbo::mean::Data<Params>;
using GP_t = limbo::model::GP<Params, Kernel_t, Mean_t>;

// an alternative is to optimize the hyper-parameters
// in that case, we need a kernel with hyper-parameters that are designed to be optimized
using Kernel2_t = limbo::kernel::SquaredExpARD<Params>;
using Mean_t = limbo::mean::Data<Params>;
using GP2_t = limbo::model::GP<Params, Kernel2_t, Mean_t, limbo::model::gp::KernelLFOpt<Params>>;

gp1.cpp:

#include "gp.h"

using namespace limbo;

void plot(GP_t& gp, GP2_t& gp_ard);

int main(int argc, char** argv)
{
    // our data (1-D inputs, 1-D outputs)
    std::vector<Eigen::VectorXd> samples;
    std::vector<Eigen::VectorXd> observations;

    size_t N = 8;
    for (size_t i = 0; i < N; i++) {
        Eigen::VectorXd s = tools::random_vector(1).array() * 4.0 - 2.0;
        samples.push_back(s);
        observations.push_back(tools::make_vector(std::cos(s(0))));
    }

    // 1-D inputs, 1-D outputs
    GP_t gp(1, 1);

    // compute the GP
    gp.compute(samples, observations);

    GP2_t gp_ard(1, 1);
    // do not forget to call the optimization!
    gp_ard.compute(samples, observations, false);
    gp_ard.optimize_hyperparams();

    plot(gp, gp_ard);

    return 0;
}

gp2.cpp:

#include "gp.h"

using namespace limbo;

void plot(GP_t& gp, GP2_t& gp_ard)
{
    // write the predicted data in a file (e.g. to be plotted)
    std::ofstream ofs("gp.dat");
    for (int i = 0; i < 100; ++i) {
        Eigen::VectorXd v = tools::make_vector(i / 100.0).array() * 4.0 - 2.0;
        Eigen::VectorXd mu;
        double sigma;
        std::tie(mu, sigma) = gp.query(v);
        // an alternative (slower) is to query mu and sigma separately:
        //  double mu = gp.mu(v)[0]; // mu() returns a 1-D vector
        //  double s2 = gp.sigma(v);
        ofs << v.transpose() << " " << mu[0] << " " << sqrt(sigma) << std::endl;
    }

    // write the predicted data in a file (e.g. to be plotted)
    std::ofstream ofs_ard("gp_ard.dat");
    for (int i = 0; i < 100; ++i) {
        Eigen::VectorXd v = tools::make_vector(i / 100.0).array() * 4.0 - 2.0;
        Eigen::VectorXd mu;
        double sigma;
        std::tie(mu, sigma) = gp_ard.query(v);
        ofs_ard << v.transpose() << " " << mu[0] << " " << sqrt(sigma) << std::endl;
    }

    // Sometimes is useful to save an optimized GP
    gp_ard.save<serialize::TextArchive>("myGP");

    // Later we can load -- we need to make sure that the type is identical to the one saved
    gp_ard.load<serialize::TextArchive>("myGP");
}

Compiler output:

➜  ThirdParty git:(master) ✗ g++ -std=c++11 gp1.cpp gp2.cpp -I. -Ilimbo/src -Ieigen -o gp
Undefined symbols for architecture x86_64:
  "boost::filesystem::detail::create_directory(boost::filesystem::path const&, boost::system::error_code*)", referenced from:
      boost::filesystem::create_directory(boost::filesystem::path const&) in gp2-d93509.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
➜  ThirdParty git:(master) ✗ g++ -std=c++11 gp1.cpp gp2.cpp -I. -Ilimbo/src -Ieigen -o gp -lboost_filesystem
duplicate symbol 'limbo::tools::par::init()' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::tools::random_lhs(int, int)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::tools::random_vector_unbounded(int)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::tools::random_vector_bounded(int)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::tools::make_vector(double)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::opt::no_grad(double)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::tools::random_vector(int, bool)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::opt::fun(std::__1::pair<double, boost::optional<Eigen::Matrix<double, -1, 1, 0, -1, 1> > > const&)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
duplicate symbol 'limbo::opt::grad(std::__1::pair<double, boost::optional<Eigen::Matrix<double, -1, 1, 0, -1, 1> > > const&)' in:
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp1-5ce4dc.o
    /var/folders/bm/wbld7cgn05ldqxlkmv19t7x80000gp/T/gp2-97c2e2.o
ld: 9 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
dmikushin commented 4 years ago

All of these go away if all of the conflicting definitions are inlined (see below). I don't know how much this is valid wrt to your other use cases. But if you want I can send this patch as a PR.

diff --git a/src/limbo/opt/optimizer.hpp b/src/limbo/opt/optimizer.hpp
index 99c242a6c..938294285 100644
--- a/src/limbo/opt/optimizer.hpp
+++ b/src/limbo/opt/optimizer.hpp
@@ -62,11 +62,11 @@ namespace limbo {

         ///@ingroup opt_tools
         ///return with opt::no_grad(your_val) if no gradient is available (to be used in functions to be optimized)
-        eval_t no_grad(double x) { return eval_t{x, boost::optional<Eigen::VectorXd>{}}; }
+        inline eval_t no_grad(double x) { return eval_t{x, boost::optional<Eigen::VectorXd>{}}; }

         ///@ingroup opt_tools
         /// get the gradient from a function evaluation (eval_t)
-        const Eigen::VectorXd& grad(const eval_t& fg)
+        inline const Eigen::VectorXd& grad(const eval_t& fg)
         {
             assert(std::get<1>(fg).is_initialized());
             return std::get<1>(fg).get();
@@ -74,7 +74,7 @@ namespace limbo {

         ///@ingroup opt_tools
         /// get the value from a function evaluation (eval_t)
-        double fun(const eval_t& fg)
+        inline double fun(const eval_t& fg)
         {
             return std::get<0>(fg);
         }
diff --git a/src/limbo/tools/math.hpp b/src/limbo/tools/math.hpp
index 292daae9e..224431778 100644
--- a/src/limbo/tools/math.hpp
+++ b/src/limbo/tools/math.hpp
@@ -61,7 +61,7 @@ namespace limbo {

         /// @ingroup tools
         /// make a 1-D vector from a double (useful when we need to return vectors)
-        Eigen::VectorXd make_vector(double x)
+        inline Eigen::VectorXd make_vector(double x)
         {
             Eigen::VectorXd res(1);
             res(0) = x;
diff --git a/src/limbo/tools/parallel.hpp b/src/limbo/tools/parallel.hpp
index 337c3275e..2ad2e3662 100644
--- a/src/limbo/tools/parallel.hpp
+++ b/src/limbo/tools/parallel.hpp
@@ -114,7 +114,7 @@ namespace limbo {
 #else
             /// @ingroup par_tools
             /// init TBB (if activated) for multi-core computing
-            void init()
+            inline void init()
             {
             }
 #endif
diff --git a/src/limbo/tools/random_generator.hpp b/src/limbo/tools/random_generator.hpp
index 54fc46818..154cad4ba 100644
--- a/src/limbo/tools/random_generator.hpp
+++ b/src/limbo/tools/random_generator.hpp
@@ -104,7 +104,7 @@ namespace limbo {
         ///
         /// - this function is thread safe because we use a random generator for each thread
         /// - we use a C++11 random number generator
-        Eigen::VectorXd random_vector_bounded(int size)
+        inline Eigen::VectorXd random_vector_bounded(int size)
         {
             static thread_local rgen_double_t rgen(0.0, 1.0);
             Eigen::VectorXd res(size);
@@ -118,7 +118,7 @@ namespace limbo {
         ///
         /// - this function is thread safe because we use a random generator for each thread
         /// - we use a C++11 random number generator
-        Eigen::VectorXd random_vector_unbounded(int size)
+        inline Eigen::VectorXd random_vector_unbounded(int size)
         {
             static thread_local rgen_gauss_t rgen(0.0, 10.0);
             Eigen::VectorXd res(size);
@@ -129,7 +129,7 @@ namespace limbo {

         /// @ingroup tools
         /// random vector wrapper for both bounded and unbounded versions
-        Eigen::VectorXd random_vector(int size, bool bounded = true)
+        inline Eigen::VectorXd random_vector(int size, bool bounded = true)
         {
             if (bounded)
                 return random_vector_bounded(size);
@@ -138,7 +138,7 @@ namespace limbo {

         /// @ingroup tools
         /// generate n random samples with Latin Hypercube Sampling (LHS) in [0, 1]^dim
-        Eigen::MatrixXd random_lhs(int dim, int n)
+        inline Eigen::MatrixXd random_lhs(int dim, int n)
         {
             Eigen::VectorXd cut = Eigen::VectorXd::LinSpaced(n + 1, 0., 1.);
             Eigen::MatrixXd u = Eigen::MatrixXd::Zero(n, dim);
costashatz commented 4 years ago

Are you sure that you are using the master branch? The inlines are already there.. See here for example.

dmikushin commented 4 years ago

I'm sorry, you are right, I mistakenly took release 2.1

costashatz commented 4 years ago

I'm sorry, you are right, I mistakenly took release 2.1

Good! Let me know if the problem still persists in master branch. We fixed a few of these inlines but maybe there are a few missing.

Btw, how is this related to the original question of the issue?

Thanks again!