Advanced Topic: Using External C++ Functions

This is based on the relevant portion of the CmdStan documentation here

Consider the following Stan model, based on the bernoulli example.

from cmdstanpy import CmdStanModel
model_external = CmdStanModel(stan_file='bernoulli_external.stan', compile=False)
functions {
  real make_odds(real theta);
data {
  int<lower=0> N;
  array[N] int<lower=0, upper=1> y;
parameters {
  real<lower=0, upper=1> theta;
model {
  theta ~ beta(1, 1); // uniform prior on interval 0, 1
  y ~ bernoulli(theta);
generated quantities {
  real odds;
  odds = make_odds(theta);

As you can see, it features a function declaration for make_odds, but no definition. If we try to compile this, we will get an error.

INFO:cmdstanpy:compiling stan program, exe file: /home/docs/checkouts/
INFO:cmdstanpy:compiler options: stanc_options={}, cpp_options={}
ERROR:cmdstanpy:Stan program failed to compile:
--- Translating Stan model to C++ code ---
bin/stanc  --o=/home/docs/checkouts/ /home/docs/checkouts/
Semantic error in '/home/docs/checkouts/', line 2, column 2 to column 29:
     1:  functions {
     2:    real make_odds(real theta);
     3:  }
     4:  data {

Some function is declared without specifying a definition.make: *** [make/program:47: /home/docs/checkouts/] Error 1

Command ['make', '/home/docs/checkouts/']
        error during processing No such file or directory

ERROR:cmdstanpy:model compilation failed

Even enabling the --allow_undefined flag to stanc3 will not allow this model to be compiled quite yet.

INFO:cmdstanpy:compiling stan program, exe file: /home/docs/checkouts/
INFO:cmdstanpy:compiler options: stanc_options={'allow_undefined': True}, cpp_options={}
ERROR:cmdstanpy:model compilation failed

To resolve this, we need to both tell the Stan compiler an undefined function is okay and let C++ know what it should be.

We can provide a definition in a C++ header file by using the user_header argument to either the CmdStanModel constructor or the compile method.

This will enables the allow_undefined flag automatically.

INFO:cmdstanpy:compiling stan program, exe file: /home/docs/checkouts/
INFO:cmdstanpy:compiler options: stanc_options={'allow_undefined': True}, cpp_options={'USER_HEADER': '/home/docs/checkouts/'}
INFO:cmdstanpy:compiled model file: /home/docs/checkouts/

We can then run this model and inspect the output

fit = model_external.sample(data={'N':10, 'y':[0,1,0,0,0,0,0,0,0,1]})
INFO:cmdstanpy:sampling: ['/home/docs/checkouts/', 'id=1', 'random', 'seed=18276', 'data', 'file=/tmp/tmpx52i_xgj/7401vp8i.json', 'output', 'file=/tmp/tmpx52i_xgj/bernoulli_external-20211019134735-1-anupw69y.csv', 'method=sample', 'algorithm=hmc', 'adapt', 'engaged=1']

INFO:cmdstanpy:sampling completed

array([0.0464677, 0.0414551, 0.0406494, ..., 0.588343 , 0.549018 ,
       0.324389 ])

The contents of this header file are a bit complicated unless you are familiar with the C++ internals of Stan, so they are presented without comment:

#include <boost/math/tools/promotion.hpp>
#include <ostream>

namespace bernoulli_model_namespace {
    template <typename T0__>  inline  typename
          make_odds(const T0__& theta, std::ostream* pstream__) {
            return theta / (1 - theta);