Custom c++ using cmdstanr?

Is this possible somehow? Spent a bit of time coding a function and then found out complex number autodiff hasn’t made it to rstan, would like to test it…

1 Like

It is possible. Its not as friendly as we would like it to be, but can be done. Not at my computer right now, but can show an example tomorrow. You need to use cmdstans user_header make flag essentialy.

2 Likes

Yeah we haven’t yet done anything in cmdstanr to make this easier than how you would have to do it with cmdstan itself, but since cmdstan supports this it should be possible to already do it via cmdstanr too. The Using External C++ Code section of the cmdstan guide has the details for getting it to work with cmdstan.

1 Like

have had a few guesses at the syntax but the closest I think I got was this

cmdstan_model('test.stan',include_paths=getwd(),
  cpp_options=list(STANCFLAGS='--allow-undedefined', USER_HEADER='<syl2.hpp>'))

Which gives me "unknown option ‘–allow-undedefined’ " and then a long list of options, among them, --allow-undefined ;)

For STANCFLAGS we have a separate argument stanc_options.

So something like this should be close (you had a typo in allow-undefined):

cmdstan_model('test.stan',include_paths=getwd(),
   cpp_options=list(USER_HEADER="syl2.hpp"), 
   stanc_options = list("allow-undefined")
)

Although I think you need to specify the full path for the user_header. Will check in an hour or so.

hah! I was staring at that for a while wondering what was wrong, not seeing the typo… though even with the full path it can’t seem to find the external code:

cmdstan_model('test.stan',include_paths=getwd(),
  cpp_options=list(USER_HEADER=file.path(getwd(),"syl2.hpp")), 
  stanc_options = list("allow-undefined")
)

Compiling Stan program...
C:/rtools40/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: 
C:/Users/Driver/AppData/Local/Temp/RtmpWo65Np/model-50442d747fa.o:model-50442d747fa.hpp:(.text$_ZNK20test_model_namespace10test_model8log_probILb0ELb0EdEET1_RSt6vectorIS2_SaIS2_EERS3_IiSaIiEEPSo[_ZNK20test_model_namespace10test_model8log_probILb0ELb0EdEET1_RSt6vectorIS2_SaIS2_EERS3_IiSaIiEEPSo]+0x135): undefined reference to `Eigen::Matrix<boost::math::tools::promote_args<double, double, float, float, float, float>::type, -1, -1, 0, -1, -1> test_model_namespace::syl<double, double>(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, std::ostream*)'

A drawback of the cmdstan’s approach to external C++ files is that the function need to be put inside the same namespace as the model.

So whatever you have in your external file, you need to place it in

namespace test_model_namespace {

// .. contents of the file

}

The namespace is always: "name of the model"+"_model_namespace".

What rstan does here is it takes the C++ file of the model, places the include inside the namespace and then compiles the model. While that approach works, I honestly dont like it very much. A cleaner solution would be to handle that in stanc compiler (see https://github.com/stan-dev/stanc3/issues/712).
Maybe there is a way around this in the meantime.

2 Likes

still no go unfortunately. Can’t be sure that it’s even finding the syl2.hpp file, the error is the same no matter which filename I have. A simpler way of doing all this would indeed be very nice!

Ok, yeah, this uncovered a cmdstan bug. USER_HEADER only works with the deprecated --allow_undefined. Argh…

Would you mind creating an issue for this on cmdstan?

This works:

library(cmdstanr)
model_code <-
"
functions {
  real my_fun(real r);
}
transformed data {
  real s = my_fun(0.0);
}
parameters {
  real y;
}
model {
  y ~ normal(0, 1);
}
"
stan_file <- write_stan_file(model_code, basename = "test.stan")
hpp_code <-
"
#include <iostream>

namespace test_model_namespace {

double my_fun(double r, std::ostream* = nullptr) {
  return r+1.0;
}

}
"
hpp_file <- "test.hpp"
write(hpp_code, file = hpp_file)
mod <- cmdstan_model(stan_file,include_paths=getwd(),
              cpp_options=list(USER_HEADER=file.path(getwd(), hpp_file)),
              stanc_options = list("allow_undefined")
)
fit <- mod$sample()

What you need to be careful with is the std::ostream* argument. Those are required for any external C++ function in Stan.

2 Likes

Thanks. I filed the issue https://github.com/stan-dev/cmdstan/issues/952

A new error now:
error: ‘Eigen’ has not been declared
Maybe I’ll wait for rstan, since the function is kind of useless without it anyway…

1 Like