Rstan::expose_stan_functions inside a package?


#1

Suppose I write some stan function like the following:

functions {
  int integer_rng(int X) {
    int out = categorical_rng(rep_vector(inv(X), X)); 
    return out;
  }
}

That function is saved in a file, integer_rng.stan. I then create a package using

library(rstantools)
rstan_package_skeleton('testStanPackage', rstudio = TRUE, open = TRUE,
                       stan_files = 'integer_rng.stan', travis = FALSE)

Now integer_rng.stan is located in testStanPackage/src/stan_files

I want to expose that function. If i was not trying to do this inside a package I would simply run rstan::expose_stan_functions('integer_rng.sta')

Alas, I have no clue how to do this inside a package. My first attempt was to create the following R function testStanPackage/R/rng_integer.R :

#' rng_integer
#'
#' @param X
#'
#' @return
#' @export
#'
#' @examples
rng_integer <- function(X = 100) {
  rstan::expose_stan_functions("src/stan_files/integer_rng.stan") # I know this is wrong, but i'm not sure what to do here
  rn <- integer_rng(X)
  return(rn)
}

Which does do what I was hoping. First, it compiles the stan code when I call it instead of when I build the package. Second it always returns the same number:

> rng_integer(X = 100)
[1] 100
> rng_integer(X = 100)
[1] 100
> rng_integer(X = 100)
[1] 100
> rng_integer(X = 100)
[1] 100
> rng_integer(X = 100)
[1] 100

What is the right way of doing this? Thanks!


#2

#3

Thanks @bgoodri . I’m having a bit of troubles understanding how to implement this. First I compile the stan code like follows:

library(rstan)
options(rccp.cpp.dir="./cache")
model_src <- stanc_builder("src/stan_files/integer_rng.stan")
expose_stan_functions(model_src)

Then I need to find the resulting .cpp file and copy it to src. I was expecting that file to be in ./cache but there is nothing there. What am I doing wrong?


#4

It might be under .cache.


#5

I don’t have that folder :(

rstudio@a19376b246e8:~/testStanPackage$ ls -la
total 24
drwxr-xr-x 1 rstudio rstudio  200 Jan 21 20:22 .
drwxr-xr-x 1 rstudio rstudio  354 Jan 21 17:18 ..
-rw-r--r-- 1 rstudio rstudio  675 Jan 21 15:05 DESCRIPTION
-rw-r--r-- 1 rstudio rstudio   11 Jan 21 15:05 .gitignore
drwxr-xr-x 1 rstudio rstudio   14 Jan 21 15:05 inst
drwxr-xr-x 1 rstudio rstudio   80 Jan 21 17:15 man
-rw-r--r-- 1 rstudio rstudio  190 Jan 21 17:15 NAMESPACE
drwxr-xr-x 1 rstudio rstudio  110 Jan 21 15:28 R
-rw-r--r-- 1 rstudio rstudio   41 Jan 21 15:05 .Rbuildignore
-rw-r--r-- 1 rstudio rstudio 1336 Jan 21 17:07 .Rhistory
drwxr-xr-x 1 rstudio rstudio   28 Jan 21 15:05 .Rproj.user
drwxr-xr-x 1 rstudio rstudio  124 Jan 21 17:20 src
-rw-r--r-- 1 rstudio rstudio  386 Jan 21 20:22 testStanPackage.Rproj
drwxr-xr-x 1 rstudio rstudio   18 Jan 21 15:05 tools

I tried creating the folder, restarting my rsession, and re running the code but the folder still empty.


#6

Maybe try specifying the cacheDir argument rather than relying on the option.


#7

Thanks @bgoodri! This did the trick:

library(rstan)
model_src <- stanc_builder("src/stan_files/integer_rng.stan")
expose_stan_functions(model_src, cacheDir = "cache")

That code generates file2586805c0a4.cpp:

#include <exporter.h>
#include <RcppEigen.h>
// Code generated by Stan version 2.18.0

#include <stan/model/standalone_functions_header.hpp>

namespace user_7952f2e16b74f9a93db69131bbf741ef { 
using std::istream;
using std::string;
using std::stringstream;
using std::vector;
using namespace stan::math;

typedef Eigen::Matrix<double,Eigen::Dynamic,1> vector_d;
typedef Eigen::Matrix<double,1,Eigen::Dynamic> row_vector_d;
typedef Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> matrix_d;

stan::io::program_reader prog_reader__() {
    stan::io::program_reader reader;
    reader.add_event(0, 0, "start", "unknown file name");
    reader.add_event(8, 6, "end", "unknown file name");
    return reader;
}

template <class RNG>
int
integer_rng(const int& X, RNG& base_rng__, std::ostream* pstream__) {
    typedef double local_scalar_t__;
    typedef int fun_return_scalar_t__;
    const static bool propto__ = true;
    (void) propto__;
        local_scalar_t__ DUMMY_VAR__(std::numeric_limits<double>::quiet_NaN());
        (void) DUMMY_VAR__;  // suppress unused var warning

    int current_statement_begin__ = -1;
    try {
        {
        current_statement_begin__ = 3;
        int out(0);
        (void) out;  // dummy to suppress unused var warning

        stan::math::fill(out, std::numeric_limits<int>::min());
        stan::math::assign(out,categorical_rng(rep_vector(inv(X),X), base_rng__));


        current_statement_begin__ = 4;
        return stan::math::promote_scalar<fun_return_scalar_t__>(out);
        }
    } catch (const std::exception& e) {
        stan::lang::rethrow_located(e, current_statement_begin__, prog_reader__());
        // Next line prevents compiler griping about no return
        throw std::runtime_error("*** IF YOU SEE THIS, PLEASE REPORT A BUG ***");
    }
}


struct integer_rng_functor__ {
    template <class RNG>
        int
    operator()(const int& X, RNG& base_rng__, std::ostream* pstream__) const {
        return integer_rng(X, base_rng__, pstream__);
    }
};

 } 
// [[Rcpp::depends(rstan)]]
// [[Rcpp::export]]
int
integer_rng(const int& X, boost::ecuyer1988& base_rng__, std::ostream* pstream__ = nullptr){
  return 
user_7952f2e16b74f9a93db69131bbf741ef::integer_rng<boost::ecuyer1988>(X, base_rng__, pstream__);
}

If I understood the answer from the other post, I need to make some modification to this .cpp file and save it in my src folder to expose the function. Could you show me what do I need to modify?


#8

It is pretty much just delete everything that is not your functions and then apply the instructions for building a package with Rcpp.


#9

I’m trying to follow this vignette to create a package with Rcpp.

I created the integer_rng.cpp:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
template <class RNG>
int
integer_rng(const int& X, RNG& base_rng__, std::ostream* pstream__) {
  typedef double local_scalar_t__;
  typedef int fun_return_scalar_t__;
  const static bool propto__ = true;
  (void) propto__;
  local_scalar_t__ DUMMY_VAR__(std::numeric_limits<double>::quiet_NaN());
  (void) DUMMY_VAR__;  // suppress unused var warning
  
  int current_statement_begin__ = -1;
  try {
    {
      current_statement_begin__ = 3;
      int out(0);
      (void) out;  // dummy to suppress unused var warning
      
      stan::math::fill(out, std::numeric_limits<int>::min());
      stan::math::assign(out,categorical_rng(rep_vector(inv(X),X), base_rng__));
      
      
      current_statement_begin__ = 4;
      return stan::math::promote_scalar<fun_return_scalar_t__>(out);
    }
  } catch (const std::exception& e) {
    stan::lang::rethrow_located(e, current_statement_begin__, prog_reader__());
    // Next line prevents compiler griping about no return
    throw std::runtime_error("*** IF YOU SEE THIS, PLEASE REPORT A BUG ***");
  }
}

When I run Rcpp::compileAttributes() RcppExports.cpp is created

// Generated by using Rcpp::compileAttributes() -> do not edit by hand
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

#include <RcppEigen.h>
#include <Rcpp.h>

using namespace Rcpp;

// integer_rng
template <class RNG> int integer_rng(const int& X, RNG& base_rng__, std::ostream* pstream__);
RcppExport SEXP _testStanPackage_integer_rng(SEXP XSEXP, SEXP base_rng__SEXP, SEXP pstream__SEXP) {
BEGIN_RCPP
    Rcpp::RObject rcpp_result_gen;
    Rcpp::RNGScope rcpp_rngScope_gen;
    Rcpp::traits::input_parameter< const int& >::type X(XSEXP);
    Rcpp::traits::input_parameter< RNG& >::type base_rng__(base_rng__SEXP);
    Rcpp::traits::input_parameter< std::ostream* >::type pstream__(pstream__SEXP);
    rcpp_result_gen = Rcpp::wrap(integer_rng(X, base_rng__, pstream__));
    return rcpp_result_gen;
END_RCPP
}

However, that command does not create the corresponding R file. What am I missing?

Thanks!


#10

My bad. It did create the R file, RcppExports.R

# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

integer_rng <- function(X, base_rng__, pstream__) {
    .Call(`_testStanPackage_integer_rng`, X, base_rng__, pstream__)
}

However, when I try to run the function it does not work:

> devtools::load_all(".")
Loading testStanPackage
Loading required package: Rcpp
> integer_rng(X = 100)
Error in integer_rng(X = 100) : 
  object '_testStanPackage_integer_rng' not found

What am I doing wrong?

Thanks!


#11

What messages do you get during the package installation for the compilation of the c++ function?


#12

Here is a link to pastebin with all the output. I also pushed all the code to github in case that is helpful. Thanks a lot for the help @sakrejda !


#13

Oh I see, rcpp won’t export a template, it has to be instantiated. Your c++ has the rng as a template parameter, did you delete the instantiation? I think Stan puts it at the bottom.


#14

@sak[quote=“sakrejda, post:13, topic:7360”]
Your c++ has the rng as a template parameter, did you delete the instantiation?
[/quote]

I feel like I tried everything I could think off and nothing worked. This is what I currently have in my integer_rng.cpp file:

#include <Rcpp.h>
#include <exporter.h>
#include <RcppEigen.h>
#include <stan/model/standalone_functions_header.hpp>
using namespace Rcpp;
using std::istream;
using std::string;
using std::stringstream;
using std::vector;
using namespace stan::math;

typedef Eigen::Matrix<double,Eigen::Dynamic,1> vector_d;
typedef Eigen::Matrix<double,1,Eigen::Dynamic> row_vector_d;
typedef Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> matrix_d;

stan::io::program_reader prog_reader__() {
  stan::io::program_reader reader;
  reader.add_event(0, 0, "start", "unknown file name");
  reader.add_event(8, 6, "end", "unknown file name");
  return reader;
}

// [[Rcpp::depends(rstan)]]
// [[Rcpp::export]]
template <class RNG>
int
integer_rng(const int& X, RNG& base_rng__, std::ostream* pstream__) {
  typedef double local_scalar_t__;
  typedef int fun_return_scalar_t__;
  const static bool propto__ = true;
  (void) propto__;
  local_scalar_t__ DUMMY_VAR__(std::numeric_limits<double>::quiet_NaN());
  (void) DUMMY_VAR__;  // suppress unused var warning

  int current_statement_begin__ = -1;
  try {
    {
      current_statement_begin__ = 3;
      int out(0);
      (void) out;  // dummy to suppress unused var warning

      stan::math::fill(out, std::numeric_limits<int>::min());
      stan::math::assign(out,categorical_rng(rep_vector(inv(X),X), base_rng__));


      current_statement_begin__ = 4;
      return stan::math::promote_scalar<fun_return_scalar_t__>(out);
    }
  } catch (const std::exception& e) {
    stan::lang::rethrow_located(e, current_statement_begin__, prog_reader__());
    // Next line prevents compiler griping about no return
    throw std::runtime_error("*** IF YOU SEE THIS, PLEASE REPORT A BUG ***");
  }
}

struct integer_rng_functor__ {
  template <class RNG>
  int
    operator()(const int& X, RNG& base_rng__, std::ostream* pstream__) const {
      return integer_rng(X, base_rng__, pstream__);
    }
};

int
  integer_rng(const int& X, boost::ecuyer1988& base_rng__, std::ostream* pstream__ = nullptr){
    return
    user_7952f2e16b74f9a93db69131bbf741ef::integer_rng<boost::ecuyer1988>(X, base_rng__, pstream__);
  }

Do I need to delete or add something??


#15

You need the template of your function (still in its namespace) and the wrapper with the fully specified use of the template (outside the namespace where rcpp can see it). Currently your template is not in the namespace where the wrapper is looking.