Stan Algorithms: Where to Start?

The Starting Point: A Unit Test

We’ll start with the unit test for the default MCMC algorithm used in Stan. The default MCMC algorithm in Stan is:

template <typename Model> int hmc_nuts_diag_e_adapt(...)
> ./runTests.py src/test/unit/services/sample/hmc_nuts_unit_e_adapt_test.cpp
Unit test output for hmc_nuts_unit_e_adapt_test.

Breaking Down the Unit Test

Even with a familiarity with C++, if you’re not familiar with Google Test, the unit test code may look pretty foreign. I’ll try my best to demystify how everything connects within the test listed above.

1. Include Statements

The include statements are L1–6 in the test file. Line 4 includes the header file generated by the Stan compiler: #include <test/test-models/good/optimization/rosenbrock.hpp>.

2. Google Test: Test Fixture

Lines 8–17 define a test fixture. This provides variables and an initial state prior to each of the 4 tests.

class ServicesSampleHmcNutsDiagEAdapt : public testing::Test {
public:
ServicesSampleHmcNutsDiagEAdapt() : model(context, 0, &model_log) {}

std::stringstream model_log;
stan::test::unit::instrumented_logger logger;
stan::test::unit::instrumented_writer init, parameter, diagnostic;
stan::io::empty_var_context context;
stan_model model;
};

3. The First Test: check that it instantiates properly

Lines 19–55 contain the first test called call_count. In Google Test, we write assertions to verify behaviors. This test checks that output callbacks are called and the correct number of times.

unsigned int random_seed = 0;
unsigned int chain = 1;
double init_radius = 0;
int num_warmup = 200;
int num_samples = 400;
int num_thin = 5;
bool save_warmup = true;
int refresh = 0;
double stepsize = 0.1;
double stepsize_jitter = 0;
int max_depth = 8;
double delta = .1;
double gamma = .1;
double kappa = .1;
double t0 = .1;
unsigned int init_buffer = 50;
unsigned int term_buffer = 50;
unsigned int window = 100;
stan::test::unit::instrumented_interrupt interrupt;
EXPECT_EQ(interrupt.call_count(), 0);
int return_code = stan::services::sample::hmc_nuts_diag_e_adapt(model, context, random_seed, chain, init_radius, num_warmup, num_samples, num_thin, save_warmup, refresh, stepsize, stepsize_jitter, max_depth, delta, gamma, kappa, t0, init_buffer, term_buffer, window, interrupt, logger, init, parameter, diagnostic);
EXPECT_EQ(0, return_code);

int num_output_lines = (num_warmup + num_samples) / num_thin;
EXPECT_EQ(num_warmup + num_samples, interrupt.call_count());
EXPECT_EQ(1, parameter.call_count("vector_string"));
EXPECT_EQ(num_output_lines, parameter.call_count("vector_double"));
EXPECT_EQ(1, diagnostic.call_count("vector_string"));
EXPECT_EQ(num_output_lines, diagnostic.call_count("vector_double"));
  1. run the algorithm code
  2. check the output

4. Tests 2–4

Tests 2 (parameter_checks), 3 (output_sizes) and 4 (output_regression) are structured identically to the first test!

We Found the Entry Point!

If you got this far, we found out how to instantiate the algorithm code. There’s a lot more we need to dig into going forward, but this is gets us to the starting point.

Running the Tests

Before getting into the depths of C++ interfaces, let’s start with getting tests instantiated and running.

Clone the Repo

git clone https://github.com/syclik/stan-algorithms.git

Update Git Submodules and Download the Stan Compiler

Option 1: Use the script in the repo

./setup.sh
git submodule update --init --recursive
mkdir -p stan/bin
curl -L "https://github.com/stan-dev/stanc3/releases/download/v2.28.1/mac-stanc" -o stan/bin/stanc
mkdir -p stan/bin
curl -L "https://github.com/stan-dev/stanc3/releases/download/v2.28.1/linux-stanc" -o stan/bin/stanc

Run the Tests

./test.sh

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store