1

I'm trying to create a R package for my own use, that is using Rcpp and whose C++ code include the Levmar library. I'm working on Windows.

The C++ code works fine when I build it using CMake for example and run it with Visual Studio. But when I put this code in my R package and try to build it, I get the following error :

levmar_example_r.o:levmar_example_r.cpp:(.text+0x281): undefined reference to `dlevmar_der'

(dlevmar_der is declared in levmar.h which is included in my R package, see below)

I have already read quite a lot of SO posts on how to build a R package with external libraries like this or this but it didn't help me to solve my problem.

The structure of my package :

bin/
  |- levmar.lib
inst/
  |- include/
      |- levmar.h
man/
R/
src/
  |- Makevars
  |- Makevars.win
  |- levmar_example_r.cpp
  |- RcppExports.cpp
src-i386/
DESCRIPTION
NAMESPACE

Content of Makevars/Makevars.win

PKG_LIBS = -L../bin -llevmar
PKG_CPPFLAGS = -I../inst/include 

The C++ code (levmar_example_r.cpp)

#include <iostream>
#include <levmar.h>
#include <math.h>
#include <Rcpp.h>

void fun(double *p, double *x, int m, int n, void *data_){
  double a = p[0];
  double b = p[1];

  double *data = (double *) data_;

  for(int i = 0; i < n; i++){
      x[i] = log(a*data[i]+b);
  }
}

void jacFun(double *p, double *jac, int m, int n, void *data_){
  double a = p[0]; 
  double b = p[1];

  double *data = (double *) data_;

  int k, l;
  for(l=k=0; l < n; l++){
    jac[k++] = data[l]/(a*data[l]+b);
    jac[k++] = 1/(a*data[l]+b);
  }

}

// [[Rcpp::export]]
void test_levmar(){
  int m = 2; // # of parameters
  int n = 40; // # of observations

  double a = 1.0;
  double b = 2.0;

  double data[] = {0.119047619047619, 0.238095238095238,    0.357142857142857, 0.476190476190476,   0.595238095238095, 0.714285714285714, 1.07142857142857, 1.42857142857143,
    0.119047619047619   ,0.238095238095238, 0.357142857142857, 0.476190476190476, 0.595238095238095, 0.714285714285714  ,1.07142857142857, 1.42857142857143 ,
    0.119047619047619,  0.238095238095238,  0.357142857142857,  0.476190476190476,  0.595238095238095,  0.714285714285714,  1.07142857142857,   1.42857142857143,
    0.119047619047619,  0.238095238095238,  0.357142857142857,  0.476190476190476   ,0.595238095238095, 0.714285714285714,  1.07142857142857,   1.42857142857143,
    0.119047619047619,  0.238095238095238   ,0.357142857142857, 0.476190476190476,  0.595238095238095,  0.714285714285714,  1.07142857142857,   1.42857142857143};

  double popti[2];
  popti[0] = a; popti[1] = b;

  double x[40];
  fun(popti, x, m, n, (void *) data);

  // algorithm parameters
  double opts[LM_OPTS_SZ], info[LM_INFO_SZ];
  opts[0]=LM_INIT_MU;
  // stopping thresholds for
  opts[1]=1E-10;       // ||J^T e||_inf
  opts[2]=1E-10;       // ||Dp||_2
  opts[3]=1E-10;       // ||e||_2
  opts[4]= LM_DIFF_DELTA; // finite difference if used

  double p[2];
  p[0] = 3.0; p[1] = 1.0;

  dlevmar_der(fun,jacFun,p,x,m,n,100,opts,info,NULL,NULL,(void *) data);

  std::cout << "Optimum found:" << std::scientific << std::setprecision(8)<< "\t"<< p[0]<< "\t" << p[1]<< std::endl;
 }

I have also tried to put all headers of the levmar library in the inst/include folder and all the .c files in a src/levmar folder and consequently remove

PKG_LIBS = -L../bin -llevmar

in Makevars/Makevars.win and add

-I src/levmar

to the PKG_CPPFLAGS but it didn't work out either.

Do you have any idea on what I should do ?

Don't hesitate to ask for precisions if I wasn't clear enough

Herrvey
  • 23
  • 1
  • 3
  • Does `PKG_LIBS = -L../bin -l:levmar` or `PKG_LIBS = ../bin/levmar.lib` work? – Ralf Stubner Mar 13 '19 at 10:15
  • Thank you for your input. The first one gives me an other bug : `C:/RBuildTools/3.4/mingw_32/bin/../lib/gcc/i686-w64-mingw32/4.9.3/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -l:levmar` and with the second one I get the same bug as before. In my opinion, the levmar.lib is actually found by the linker as far as I got a incompatibility bug message when I first compiled the Levmar library for a 32-bit target, which doesn't appear now that I compiled it for a 64-bit target. – Herrvey Mar 13 '19 at 10:33
  • Ok. How did you compile `levmar.lib`? – Ralf Stubner Mar 13 '19 at 10:35
  • I used CMake with the library-provided CMakeLists.txt to generate the files for Visual Studio and then I compiled with Visual Studio, which created the levmar.lib file – Herrvey Mar 13 '19 at 10:47
  • 1
    VC and gcc have incompatible C++ ABIs. You have to compile the library with gcc, preferably the one from Rtools. It might be easiest to just add the sources for the library to the package and compile them during package build, c.f. https://stackoverflow.com/a/53638867/8416610. – Ralf Stubner Mar 13 '19 at 10:50
  • That may be indeed be the cause of my bug. I already tried to add the sources for the library to the package and compile them during package build (cf end of my post) but it didn't change anything. I'll try to compile with gcc using Rtools – Herrvey Mar 13 '19 at 11:05

1 Answers1

3

SODD got the better of me. I have build a very rough package that compiles the levmar code and creates an initial R package from it: https://github.com/rstub/levmaR. Important points:

  • Source files in sub-directories of src are not automatically compiled. One has to add them somehow, e.g.

    CXX_STD = CXX11
    PKG_LIBS=-L. -llevmar  $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
    PKG_CPPFLAGS=-I./levmar/ -DSTRICT_R_HEADERS 
    
    all: $(SHLIB)
    $(SHLIB): liblevmar.a
    
    LIBOBJS = levmar/lm.o levmar/Axb.o levmar/misc.o levmar/lmlec.o levmar/lmbc.o \
              levmar/lmblec.o levmar/lmbleic.o
    
    liblevmar.a: $(LIBOBJS)
        $(AR) rcs liblevmar.a $(LIBOBJS)
    
  • By default levmar tries to build single- and double-precision functions and tries to use LAPACK. Default builds of R only include double-precision LAPACK and BLAS. I disabled the single precision build.

  • The levmar library is actually pure C. So my suspicion that your problems where caused by the different C++ ABIs between VC and gcc is probably not correct. Most likely there is some other incompatibility between VC and gcc concerning the layout of static libraries.

Right now the only available function is your test_levmar(). Tested on Linux and Windows (via Appveyor and rhub).

Community
  • 1
  • 1
Ralf Stubner
  • 26,263
  • 3
  • 40
  • 75
  • Thanks a lot for this tailored answer. As it stands, it works exactly like I want to. I really do appreciate you took the time to post a working code on Github. – Herrvey Mar 14 '19 at 09:37
  • @Herrvey You are welcome. Feel free to ping me on GH if you are interested in developing this for more than "my own use". – Ralf Stubner Mar 14 '19 at 09:47