Search code examples
c++rrcpp

rcpp package compilation process map to cxxfunction compilation


I wrote an R package that compiles user-written c++ code with the help of Rcpp and RcppEigen. Every time the user wants to create a new model, he/she must write three files:

Currently, all three files are saved in the src/ directory, and compilation is triggered by calling devtools::load_all(".") After compilation, the functions callable from R are exported to NAMESPACE. Everything compiles just fine in this package framework.

Unfortunately, though, I think I designed this package incorrectly. I expect the users to change these files frequently, and different users to create different models than other users. So, I would like to save these files outside of the package directory (easy), and to compile user-written code with cxxfunplus::cxxfunctionplus() (I stole this idea from RStan's rstan::stan_model()). I also wrote my inline plugin based off of their inline plugin.

So how does my package compilation map to cxxfunction compilation?

My current attempt is below, but I'm pretty sure this is way off. The error I'm getting is

Error in compileCode(f, code, language = language, verbose = verbose) : 
  file25a041c179ba.cpp:12:10: fatal error: pf/include/pf/bootstrap_filter_with_covariates.h: No such file or directory   12 | #include "pf/include/pf/bootstrap_filter_with_covariates.h" // the boostrap particle filter      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~compilation terminated.make: *** [/usr/lib/R/etc/Makeconf:177: file25a041c179ba.o] Error 1

Stan has a line like this in their plugin that finds all their header files (e.g. system.file("include", "stan", "math", "prim", package = "StanHeaders", mustWork = TRUE)). However, my package's header files don't appear to be installed anywhere in that same directory. Why don't my headers get installed?

src <- r"(// replace every instance of <TODO> with your own code! 

#include "svol_leverage_bswc.h" 

Svol_leverageBSWC::Svol_leverageBSWC(double phi, double mu, double sigma, double rho)
{
  m_params << phi, mu, sigma, rho;
}


auto Svol_leverageBSWC::q1Samp(const ovec &y1, const cvec& z1) -> svec
{
  // phi, mu, sigma, rho
  svec x1samp;
  x1samp(0) = m_example_stdNormSampler.sample() * m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0));
  return x1samp;
}


auto Svol_leverageBSWC::fSamp(const svec &xtm1, const cvec& zt) -> svec
{
  // phi, mu, sigma, rho
  svec xt;
  double mean = m_params(1) + m_params(0) * (xtm1(0) - m_params(1)) +
    zt(0) * m_params(3) * m_params(2) * std::exp(-.5 * xtm1(0));
  xt(0) = mean + m_example_stdNormSampler.sample() * m_params(2) * std::sqrt(1.0 - m_params(3) * m_params(3));
  return xt;
}


double Svol_leverageBSWC::logGEv(const ovec &yt, const svec &xt, const cvec& zt)
{
  return rveval::evalUnivNorm<double>(
    yt(0),
    0.0,
    std::exp(.5 * xt(0)),
    true);
}



double Svol_leverageBSWC::logMuEv(const svec &x1, const cvec& z1)
{
  // parameter order phi, mu, sigma, rho
  return rveval::evalUnivNorm<double>(
    x1(0),
    0.0,
    m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0)),
    true);
}


double Svol_leverageBSWC::logQ1Ev(const svec &x1, const ovec &y1, const cvec& z1)
{
  // parameter order phi, mu, sigma, rho
  return rveval::evalUnivNorm<double>(
    x1(0),
    0.0,
    m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0)),
    true);  
}
)"

header <- r"(// replace every instance of <TODO> with your own code! 

#ifndef SVOL_LEVERAGE_BSWC_H
#define SVOL_LEVERAGE_BSWC_H

#include "pf/include/pf/bootstrap_filter_with_covariates.h" // the boostrap particle filter
#include "pf/include/pf/rv_samp.h"                              // for sampling random numbers
#include "pf/include/pf/rv_eval.h"                              // for evaluating densities and pmfs
#include "pf/include/pf/resamplers.h"                         // for resampling classes

using namespace pf;
using namespace pf::filters;
using namespace pf::resamplers;

#define nparts_SVOL_LEVERAGE_BSWC 500    // number of particles
#define dimstate_SVOL_LEVERAGE_BSWC 1  // dimension of state vectors
#define dimobs_SVOL_LEVERAGE_BSWC 1    // dimension of observation vectors
#define dimcov_SVOL_LEVERAGE_BSWC 1    // dimension of covariate vectors
#define dimparam_SVOL_LEVERAGE_BSWC 4  // dimension of parameters

// helper type aliases
using resampT   = mn_resamp_fast1<nparts_SVOL_LEVERAGE_BSWC,dimstate_SVOL_LEVERAGE_BSWC,double>;
using svec      = Eigen::Matrix  <double, dimstate_SVOL_LEVERAGE_BSWC,1>;
using ovec      = Eigen::Matrix  <double, dimobs_SVOL_LEVERAGE_BSWC,1>;
using cvec      = Eigen::Matrix  <double, dimcov_SVOL_LEVERAGE_BSWC,1>;
using param_vec = Eigen::Matrix  <double, dimparam_SVOL_LEVERAGE_BSWC,1>;
using DynMat    = Eigen::Matrix  <double, Eigen::Dynamic, Eigen::Dynamic>;
using func      = std::function  <const DynMat(const svec&, const cvec&)>;
using BasePF    = BSFilterWC     <nparts_SVOL_LEVERAGE_BSWC, dimstate_SVOL_LEVERAGE_BSWC, dimobs_SVOL_LEVERAGE_BSWC, dimcov_SVOL_LEVERAGE_BSWC, resampT, double, true>;


/**
 * @brief a particle filter class template for a TODO model
 *
 */
class Svol_leverageBSWC : public BasePF
{
private:

  // example parameter
  param_vec m_params;

  // use this for sampling
  rvsamp::UnivNormSampler<double> m_example_stdNormSampler; 
  
  // required by algorithm and required to define your model
  double logQ1Ev (const svec &x1,   const ovec &y1,  const cvec &z1 );
  double logMuEv (const svec &x1,   const cvec &z1                  );
  double logGEv  (const ovec &yt,   const svec &xt,  const cvec& zt );
  auto   q1Samp  (const ovec &y1,   const cvec& z1                  ) -> svec;
  auto   fSamp   (const svec &xtm1, const cvec &zt                  ) -> svec;
  
public:

  // constructor
  Svol_leverageBSWC(double phi, double mu, double sigma, double rho);
  
};

#endif  // SVOL_LEVERAGE_BSWC_H)"

#   //' @useDynLib pfr, .registration = TRUE
# //' @import RcppEigen
#   //' @importFrom Rcpp evalCpp
# //' @export
#   // [[Rcpp::export]]
#   double svol_leverage_bswc_approx_LL(const Rcpp::NumericVector& obsTS, const Rcpp::NumericVector& params){
#     
    
body <- r"(
  // instantiate model with arg params
  Svol_leverageBSWC mod(params[0],params[1],params[2],params[3]);

  // calculate likelihood by iterating through time series
  double ans(0.0);
  ovec yt, ytm1;
  for (int i = 1; i < obsTS.length(); i++){
    yt << obsTS[i]; ytm1 << obsTS[i-1];
    mod.filter(yt, ytm1);
    ans += mod.getLogCondLike();
  }
  return ans;
)"



#inline::registerPlugin("pfr", pfrplugin)
thisMod <- cxxfunplus::cxxfunctionplus(signature(), body = body, plugin = "pfr", includes = header, verbose = T)

Solution

  • Following the Rcpp modules vignette and jamming it all in includes= works:

    
    
    inc <- r"(
    #ifndef SVOL_LEVERAGE_BSWC_H
    #define SVOL_LEVERAGE_BSWC_H
    
    #include "pf/bootstrap_filter_with_covariates.h" // the boostrap particle filter
    #include "pf/rv_samp.h"                                 // for sampling random numbers
    #include "pf/rv_eval.h"                                 // for evaluating densities and pmfs
    #include "pf/resamplers.h"                        // for resampling classes
    
    using namespace pf;
    using namespace pf::filters;
    using namespace pf::resamplers;
    
    #define nparts_SVOL_LEVERAGE_BSWC 500    // number of particles
    #define dimstate_SVOL_LEVERAGE_BSWC 1  // dimension of state vectors
    #define dimobs_SVOL_LEVERAGE_BSWC 1    // dimension of observation vectors
    #define dimcov_SVOL_LEVERAGE_BSWC 1    // dimension of covariate vectors
    #define dimparam_SVOL_LEVERAGE_BSWC 4  // dimension of parameters
    
    // helper type aliases
    using resampT   = mn_resamp_fast1<nparts_SVOL_LEVERAGE_BSWC,dimstate_SVOL_LEVERAGE_BSWC,double>;
    using svec      = Eigen::Matrix  <double, dimstate_SVOL_LEVERAGE_BSWC,1>;
    using ovec      = Eigen::Matrix  <double, dimobs_SVOL_LEVERAGE_BSWC,1>;
    using cvec      = Eigen::Matrix  <double, dimcov_SVOL_LEVERAGE_BSWC,1>;
    using param_vec = Eigen::Matrix  <double, dimparam_SVOL_LEVERAGE_BSWC,1>;
    using DynMat    = Eigen::Matrix  <double, Eigen::Dynamic, Eigen::Dynamic>;
    using func      = std::function  <const DynMat(const svec&, const cvec&)>;
    using BasePF    = BSFilterWC     <nparts_SVOL_LEVERAGE_BSWC, dimstate_SVOL_LEVERAGE_BSWC, dimobs_SVOL_LEVERAGE_BSWC, dimcov_SVOL_LEVERAGE_BSWC, resampT, double, true>;
    
    
    /**
     * @brief a particle filter class template for a TODO model
     *
     */
    class Svol_leverageBSWC : public BasePF
    {
    private:
    
      // example parameter
      param_vec m_params;
    
      // use this for sampling
      rvsamp::UnivNormSampler<double> m_example_stdNormSampler; 
      
      // required by algorithm and required to define your model
      double logQ1Ev (const svec &x1,   const ovec &y1,  const cvec &z1 );
      double logMuEv (const svec &x1,   const cvec &z1                  );
      double logGEv  (const ovec &yt,   const svec &xt,  const cvec& zt );
      auto   q1Samp  (const ovec &y1,   const cvec& z1                  ) -> svec;
      auto   fSamp   (const svec &xtm1, const cvec &zt                  ) -> svec;
      
    public:
    
      // constructor
      Svol_leverageBSWC(double phi, double mu, double sigma, double rho);
      
    };
    
    #endif  // SVOL_LEVERAGE_BSWC_H
      
      
    double svol_leverage_bswc_approx_LL(const Rcpp::NumericVector& obsTS, const Rcpp::NumericVector& params){
    
        // instantiate model with arg params
      Svol_leverageBSWC mod(params[0],params[1],params[2],params[3]);
    
      // calculate likelihood by iterating through time series
      double ans(0.0);
      ovec yt, ytm1;
      for (int i = 1; i < obsTS.length(); i++){
        yt << obsTS[i]; ytm1 << obsTS[i-1];
        mod.filter(yt, ytm1);
        ans += mod.getLogCondLike();
      }
      return ans;
      
    }
    
    Svol_leverageBSWC::Svol_leverageBSWC(double phi, double mu, double sigma, double rho)
    {
      m_params << phi, mu, sigma, rho;
    }
    
    
    auto Svol_leverageBSWC::q1Samp(const ovec &y1, const cvec& z1) -> svec
    {
      // phi, mu, sigma, rho
      svec x1samp;
      x1samp(0) = m_example_stdNormSampler.sample() * m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0));
      return x1samp;
    }
    
    
    auto Svol_leverageBSWC::fSamp(const svec &xtm1, const cvec& zt) -> svec
    {
      // phi, mu, sigma, rho
      svec xt;
      double mean = m_params(1) + m_params(0) * (xtm1(0) - m_params(1)) +
        zt(0) * m_params(3) * m_params(2) * std::exp(-.5 * xtm1(0));
      xt(0) = mean + m_example_stdNormSampler.sample() * m_params(2) * std::sqrt(1.0 - m_params(3) * m_params(3));
      return xt;
    }
    
    
    double Svol_leverageBSWC::logGEv(const ovec &yt, const svec &xt, const cvec& zt)
    {
      return rveval::evalUnivNorm<double>(
        yt(0),
        0.0,
        std::exp(.5 * xt(0)),
        true);
    }
    
    
    double Svol_leverageBSWC::logMuEv(const svec &x1, const cvec& z1)
    {
      // parameter order phi, mu, sigma, rho
      return rveval::evalUnivNorm<double>(
        x1(0),
        0.0,
        m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0)),
        true);
    }
    
    
    double Svol_leverageBSWC::logQ1Ev(const svec &x1, const ovec &y1, const cvec& z1)
    {
      // parameter order phi, mu, sigma, rho
      return rveval::evalUnivNorm<double>(
        x1(0),
        0.0,
        m_params(2) / std::sqrt(1.0 - m_params(0) * m_params(0)),
        true);  
    }
    
    
    
    )"
    
    inline::registerPlugin("pfr", pfrplugin)
    yay <- cxxfunplus::cxxfunctionplus(signature(), plugin = "pfr", includes = inc)