- Notifications
You must be signed in to change notification settings - Fork46
A C++ library of Markov Chain Monte Carlo (MCMC) methods
License
kthohr/mcmc
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
MCMCLib is a lightweight C++ library of Markov Chain Monte Carlo (MCMC) methods.
Features:
- A C++11/14/17 library of well-known MCMC algorithms.
- Parallelized samplers designed for multi-modal distributions, including:
- Adaptive Equi-Energy Sampler (AEES)
- Differential Evolution (DE)
- For fast and efficient matrix-based computation, MCMCLib supports the following templated linear algebra libraries:
- Automatic differentiation functionality is available through use of theAutodiff library
- OpenMP-accelerated algorithms for parallel computation.
- Straightforward linking with parallelized BLAS libraries, such asOpenBLAS.
- Available as a single precision (
float
) or double precision (double
) library. - Available as a header-only library, or as a compiled shared library.
- Released under a permissive, non-GPL license.
- Algorithms
- Documentation
- General API
- Installation
- R Compatibility
- Examples
- Automatic Differentiation
- Author and License
A list of currently available algorithms includes:
- Adaptive Equi-Energy Sampler (AEES)
- Differential Evolution (DE-MCMC)
- Hamiltonian Monte Carlo (HMC)
- Metropolis-adjusted Langevin algorithm (MALA)
- No-U-Turn Sampler (NUTS)
- Random Walk Metropolis-Hastings (RWMH)
- Riemannian Manifold Hamiltonian Monte Carlo (RM-HMC)
Full documentation is available online:
A PDF version of the documentation is availablehere.
The MCMCLib API follows a relatively simple convention, with most algorithms called in the following manner:
algorithm_id(<initial values>, <log posterior kernel function of the target distribution>, <storage for posterior draws>, <additional data for the log posterior kernel function>);
The inputs, in order, are:
- A vector of initial values used to define the starting point of the algorithm.
- A user-specified function that returns the log posterior kernel value of the target distribution.
- An array to store the posterior draws.
- The final input is optional: it is any object that contains additional data necessary to evaluate the log posterior kernel function.
For example, the RWMH algorithm is called using:
boolrwmh(const ColVec_t& initial_vals, std::function<fp_t (const ColVec_t& vals_inp,void* target_data)> target_log_kernel, Mat_t& draws_out, void* target_data);
whereColVec_t
is used to represent, e.g.,arma::vec
orEigen::VectorXd
types.
MCMCLib is available as a compiled shared library, or as header-only library, for Unix-alike systems only (e.g., popular Linux-based distros, as well as macOS). Use of this library with Windows-based systems, with or without MSVC,is not supported.
MCMCLib requires either the Armadillo or Eigen C++ linear algebra libraries. (Note that Eigen version 3.4.0 requires a C++14-compatible compiler.)
Before including the header files, defineone of the following:
#defineMCMC_ENABLE_ARMA_WRAPPERS#defineMCMC_ENABLE_EIGEN_WRAPPERS
Example:
#defineMCMC_ENABLE_EIGEN_WRAPPERS#include"mcmc.hpp"
The library can be installed on Unix-alike systems via the standard./configure && make
method.
First clone the library and any necessary submodules:
# clone mcmc into the current directorygit clone https://github.com/kthohr/mcmc ./mcmc# change directorycd ./mcmc# clone necessary submodulesgit submodule update --init
Set (one) of the following environment variablesbefore runningconfigure
:
export ARMA_INCLUDE_PATH=/path/to/armadilloexport EIGEN_INCLUDE_PATH=/path/to/eigen
Finally:
# build and install with Eigen./configure -i"/usr/local" -l eigen -pmakemake install
The final command will install MCMCLib into/usr/local
.
Configuration options (see./configure -h
):
Primary
-h
print help-i
installation path; default: the build directory-f
floating-point precision mode; default:double
-l
specify the choice of linear algebra library; choosearma
oreigen
-m
specify the BLAS and Lapack libraries to link with; for example,-m "-lopenblas"
or-m "-framework Accelerate"
-o
compiler optimization options; defaults to-O3 -march=native -ffp-contract=fast -flto -DARMA_NO_DEBUG
-p
enable OpenMP parallelization features (recommended)
Secondary
-c
a coverage build (used with Codecov)-d
a 'development' build-g
a debugging build (optimization flags set to-O0 -g
)
Special
--header-only-version
generate a header-only version of MCMCLib (seebelow)
MCMCLib is also available as a header-only library (i.e., without the need to compile a shared library). Simply runconfigure
with the--header-only-version
option:
./configure --header-only-version
This will create a new directory,header_only_version
, containing a copy of MCMCLib, modified to work on an inline basis. With this header-only version, simply include the header files (#include "mcmc.hpp
) and set the include path to thehead_only_version
directory (e.g.,-I/path/to/mcmclib/header_only_version
).
To use MCMCLib with an R package, first generate a header-only version of the library (seeabove). Then simply add a compiler definition before including the MCMCLib files.
- For RcppArmadillo:
#defineMCMC_USE_RCPP_ARMADILLO#include"mcmc.hpp"
At this time, builds usingRcppEigen
are not supported as MCMCLib requires a version of Eigen >= v3.4.0.
To illustrate MCMCLib at work, consider the problem of sampling values of the mean parameter of a normal distribution.
Code:
#defineMCMC_ENABLE_EIGEN_WRAPPERS#include"mcmc.hpp"inlineEigen::VectorXdeigen_randn_colvec(size_t nr){static std::mt19937 gen{ std::random_device{}() };static std::normal_distribution<> dist;return Eigen::VectorXd{ nr }.unaryExpr([&](double x) { (void)(x);returndist(gen); });}structnorm_data_t {double sigma; Eigen::VectorXd x;double mu_0;double sigma_0;};doublell_dens(const Eigen::VectorXd& vals_inp,void* ll_data){constdoublepi =3.14159265358979;//constdouble mu =vals_inp(0);norm_data_t* dta =reinterpret_cast<norm_data_t*>(ll_data);constdouble sigma = dta->sigma;const Eigen::VectorXd x = dta->x;constint n_vals = x.size();//constdouble ret = - n_vals * (0.5 *std::log(2*pi) +std::log(sigma)) - (x.array() - mu).pow(2).sum() / (2*sigma*sigma);//return ret;}doublelog_pr_dens(const Eigen::VectorXd& vals_inp,void* ll_data){constdoublepi =3.14159265358979;//norm_data_t* dta =reinterpret_cast<norm_data_t* >(ll_data);constdouble mu_0 = dta->mu_0;constdouble sigma_0 = dta->sigma_0;constdouble x =vals_inp(0);constdouble ret = -0.5*std::log(2*pi) -std::log(sigma_0) -std::pow(x - mu_0,2) / (2*sigma_0*sigma_0);return ret;}doublelog_target_dens(const Eigen::VectorXd& vals_inp,void* ll_data){returnll_dens(vals_inp,ll_data) +log_pr_dens(vals_inp,ll_data);}intmain(){constint n_data =100;constdouble mu =2.0;norm_data_t dta; dta.sigma =1.0; dta.mu_0 =1.0; dta.sigma_0 =2.0; Eigen::VectorXd x_dta = mu +eigen_randn_colvec(n_data).array(); dta.x = x_dta; Eigen::VectorXdinitial_val(1);initial_val(0) =1.0;// mcmc::algo_settings_t settings; settings.rwmh_settings.par_scale =0.4; settings.rwmh_settings.n_burnin_draws =2000; settings.rwmh_settings.n_keep_draws =2000;// Eigen::MatrixXd draws_out;mcmc::rwmh(initial_val, log_target_dens, draws_out, &dta, settings);// std::cout <<"rwmh mean:\n" << draws_out.colwise().mean() << std::endl; std::cout <<"acceptance rate:" <<static_cast<double>(settings.rwmh_settings.n_accept_draws) / settings.rwmh_settings.n_keep_draws << std::endl;//return0;}
On x86-based computers, this example can be compiled using:
g++ -Wall -std=c++14 -O3 -mcpu=native -ffp-contract=fast -I$EIGEN_INCLUDE_PATH -I./../../include/ rwmh_normal_mean.cpp -o rwmh_normal_mean.out -L./../.. -lmcmc
Check the/examples
directory for additional examples, andhttps://mcmclib.readthedocs.io/en/latest/ for a detailed description of each algorithm.
By combining Eigen with theAutodiff library, MCMCLib provides experimental support for automatic differentiation.
The example below uses forward-mode automatic differentiation to compute the gradient of the Gaussian likelihood function, and the HMC algorithm to sample from the posterior distribution of the mean and variance parameters.
#defineMCMC_ENABLE_EIGEN_WRAPPERS#include"mcmc.hpp"#include<autodiff/forward/real.hpp>#include<autodiff/forward/real/eigen.hpp>inlineEigen::VectorXdeigen_randn_colvec(size_t nr){static std::mt19937 gen{ std::random_device{}() };static std::normal_distribution<> dist;return Eigen::VectorXd{ nr }.unaryExpr([&](double x) { (void)(x);returndist(gen); });}structnorm_data_t { Eigen::VectorXd x;};doublell_dens(const Eigen::VectorXd& vals_inp, Eigen::VectorXd* grad_out,void* ll_data){constdoublepi =3.14159265358979;norm_data_t* dta =reinterpret_cast<norm_data_t*>(ll_data);const Eigen::VectorXd x = dta->x;// autodiff::real u; autodiff::ArrayXreal xd = vals_inp.eval(); std::function<autodiff::real (const autodiff::ArrayXreal& vals_inp)> normal_dens_log_form \ = [x,pi](const autodiff::ArrayXreal& vals_inp) -> autodiff::real { autodiff::real mu =vals_inp(0); autodiff::real sigma =vals_inp(1);return - x.size() * (0.5 *std::log(2*pi) +autodiff::detail::log(sigma)) - (x.array() - mu).pow(2).sum() / (2*sigma*sigma); };//if (grad_out) { Eigen::VectorXd grad_tmp =autodiff::gradient(normal_dens_log_form,autodiff::wrt(xd),autodiff::at(xd), u); *grad_out = grad_tmp; }else { u =normal_dens_log_form(xd); }//return u.val();}doublelog_target_dens(const Eigen::VectorXd& vals_inp, Eigen::VectorXd* grad_out,void* ll_data){returnll_dens(vals_inp,grad_out,ll_data);}intmain(){constint n_data =1000;constdouble mu =2.0;constdouble sigma =2.0;norm_data_t dta; Eigen::VectorXd x_dta = mu + sigma *eigen_randn_colvec(n_data).array(); dta.x = x_dta; Eigen::VectorXdinitial_val(2);initial_val(0) = mu +1;// muinitial_val(1) = sigma +1;// sigma mcmc::algo_settings_t settings; settings.hmc_settings.step_size =0.08; settings.hmc_settings.n_burnin_draws =2000; settings.hmc_settings.n_keep_draws =2000;// Eigen::MatrixXd draws_out;mcmc::hmc(initial_val, log_target_dens, draws_out, &dta, settings);// std::cout <<"hmc mean:\n" << draws_out.colwise().mean() << std::endl; std::cout <<"acceptance rate:" <<static_cast<double>(settings.hmc_settings.n_accept_draws) / settings.hmc_settings.n_keep_draws << std::endl;//return0;}
Compile with:
g++ -Wall -std=c++17 -O3 -march=native -ffp-contract=fast -I/path/to/eigen -I/path/to/autodiff -I/path/to/mcmc/include hmc_normal_autodiff.cpp -o hmc_normal_autodiff.cpp -L/path/to/mcmc/lib -lmcmc
See thedocumentation for more details on this topic.
Keith O'Hara
Apache Version 2