0
votes

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

1
Does PKG_LIBS = -L../bin -l:levmar or PKG_LIBS = ../bin/levmar.lib work? - Ralf Stubner
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
Ok. How did you compile levmar.lib? - Ralf Stubner
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
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. stackoverflow.com/a/53638867/8416610. - Ralf Stubner

1 Answers

2
votes

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).