Skip to content

Commit 9539796

Browse files
No-U-Turn Sampler in HMC (GeomScale#216)
* initialize nuts sampler class * implement the burnin of nuts sampler * add tests and resolve bugs * implement e0 estimation in burnin of nuts * optimize leapfrog * integrate nuts into the R interface * document random walk in sample_points.cpp in R interface * fix burnin for the non-truncated case * resolve bugs in hmc and nuts pipelines * improve the preprocess in burin step of nuts * split large lines in sample_points.cpp * Add NUTS C++ example and update CMake (#29) * resolve PR comments * fix minor bug * fix compiler bug * fix error in building the C++ examples * resolve warnings in sample_points * fix lpsolve cran warning * fix cran warning on mac * improve lpsolve cmake for cran check * fix R warning in mac test * remove lpsolve header * resolve PR comments --------- Co-authored-by: Marios Papachristou <[email protected]> Co-authored-by: Apostolos Chalkis (TolisChal) <[email protected]>
1 parent 9cfdfe1 commit 9539796

File tree

17 files changed

+732
-78
lines changed

17 files changed

+732
-78
lines changed

R-proj/R/RcppExports.R

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,6 @@ sample_points <- function(P, n, random_walk = NULL, distribution = NULL, seed =
361361
.Call(`_volesti_sample_points`, P, n, random_walk, distribution, seed)
362362
}
363363

364-
#' @export
365-
sample_spectra <- function(file = NULL, N = NULL, walk_length = NULL) {
366-
.Call(`_volesti_sample_spectra`, file, N, walk_length)
367-
}
368-
369364
#' Write a SDPA format file
370365
#'
371366
#' Outputs a spectrahedron (the matrices defining a linear matrix inequality) and a vector (the objective function)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# VolEsti (volume computation and sampling library)
2+
3+
# Copyright (c) 2012-2020 Vissarion Fisikopoulos
4+
# Copyright (c) 2018-2020 Apostolos Chalkis
5+
# Copyright (c) 2020-2020 Marios Papachristou
6+
7+
# Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program.
8+
9+
# Licensed under GNU LGPL.3, see LICENCE file
10+
11+
# Example script for using the logconcave sampling methods
12+
13+
# Import required libraries
14+
library(ggplot2)
15+
library(volesti)
16+
17+
# Sampling from logconcave density example
18+
19+
# Helper function
20+
norm_vec <- function(x) sqrt(sum(x^2))
21+
22+
# Negative log-probability oracle
23+
f <- function(x) (norm_vec(x)^2 + sum(x))
24+
25+
# Negative log-probability gradient oracle
26+
grad_f <- function(x) (2 * x + 1)
27+
28+
dimension <- 50
29+
facets <- 200
30+
31+
# Create domain of truncation
32+
H <- gen_rand_hpoly(dimension, facets, seed = 15)
33+
34+
# Rounding
35+
Tr <- rounding(H, seed = 127)
36+
37+
P <- Hpolytope$new(A = Tr$Mat[1:nrow(Tr$Mat), 2:ncol(Tr$Mat)], b = Tr$Mat[,1])
38+
39+
x_min = matrix(0, dimension, 1)
40+
41+
# Warm start point from truncated Gaussian
42+
warm_start <- sample_points(P, n = 1, random_walk = list("nburns" = 5000), distribution = list("density" = "gaussian", "variance" = 1/2, "mode" = x_min))
43+
44+
# Sample points
45+
n_samples <- 20000
46+
47+
samples <- sample_points(P, n = n_samples, random_walk = list("walk" = "NUTS", "solver" = "leapfrog", "starting_point" = warm_start[,1]),
48+
distribution = list("density" = "logconcave", "negative_logprob" = f, "negative_logprob_gradient" = grad_f))
49+
50+
# Plot histogram
51+
hist(samples[1,], probability=TRUE, breaks = 100)
52+
53+
psrfs <- psrf_univariate(samples)
54+
n_ess <- ess(samples)
55+

R-proj/examples/logconcave/simple_hmc_rand_poly.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ dimension <- 50
2929
facets <- 200
3030

3131
# Create domain of truncation
32-
H <- gen_rand_hpoly(dimension, facets)
32+
H <- gen_rand_hpoly(dimension, facets, seed = 15)
3333

3434
# Rounding
35-
Tr <- rounding(H)
35+
Tr <- rounding(H, seed = 127)
3636

3737
P <- Hpolytope$new(A = Tr$Mat[1:nrow(Tr$Mat), 2:ncol(Tr$Mat)], b = Tr$Mat[,1])
3838

R-proj/man/sample_points.Rd

Lines changed: 11 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

R-proj/src/sample_points.cpp

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ enum random_walks {
3636
brdhr,
3737
bcdhr,
3838
hmc,
39+
nuts,
3940
gaussian_hmc,
4041
exponential_hmc,
4142
uld
@@ -213,6 +214,26 @@ void sample_from_polytope(Polytope &P, int type, RNGType &rng, PointList &randPo
213214
}
214215

215216
break;
217+
case nuts:
218+
219+
logconcave_sampling <
220+
PointList,
221+
Polytope,
222+
RNGType,
223+
NutsHamiltonianMonteCarloWalk,
224+
NT,
225+
Point,
226+
NegativeGradientFunctor,
227+
NegativeLogprobFunctor,
228+
LeapfrogODESolver <
229+
Point,
230+
NT,
231+
Polytope,
232+
NegativeGradientFunctor
233+
>
234+
>(randPoints, P, rng, walkL, numpoints, StartingPoint, nburns, *F, *f);
235+
236+
break;
216237
case uld:
217238

218239
logconcave_sampling <
@@ -244,7 +265,16 @@ void sample_from_polytope(Polytope &P, int type, RNGType &rng, PointList &randPo
244265
//' @param n The number of points that the function is going to sample from the convex polytope.
245266
//' @param random_walk Optional. A list that declares the random walk and some related parameters as follows:
246267
//' \itemize{
247-
//' \item{\code{walk} }{ A string to declare the random walk: i) \code{'CDHR'} for Coordinate Directions Hit-and-Run, ii) \code{'RDHR'} for Random Directions Hit-and-Run, iii) \code{'BaW'} for Ball Walk, iv) \code{'BiW'} for Billiard walk, v) \code{'dikin'} for dikin walk, vi) \code{'vaidya'} for vaidya walk, vii) \code{'john'} for john walk, viii) \code{'BCDHR'} boundary sampling by keeping the extreme points of CDHR or ix) \code{'BRDHR'} boundary sampling by keeping the extreme points of RDHR x) \code{'HMC'} for Hamiltonian Monte Carlo (logconcave densities) xi) \code{'ULD'} for Underdamped Langevin Dynamics using the Randomized Midpoint Method xii) \code{'ExactHMC'} for exact Hamiltonian Monte Carlo with reflections (spherical Gaussian or exponential distribution). The default walk is \code{'aBiW'} for the uniform distribution or \code{'CDHR'} for the Gaussian distribution and H-polytopes and \code{'BiW'} or \code{'RDHR'} for the same distributions and V-polytopes and zonotopes.}
268+
//' \item{\code{walk} }{ A string to declare the random walk: i) \code{'CDHR'} for Coordinate Directions Hit-and-Run,
269+
//' ii) \code{'RDHR'} for Random Directions Hit-and-Run, iii) \code{'BaW'} for Ball Walk,
270+
//' iv) \code{'BiW'} for Billiard walk, v) \code{'dikin'} for dikin walk, vi) \code{'vaidya'} for vaidya walk,
271+
//' vii) \code{'john'} for john walk, viii) \code{'BCDHR'} boundary sampling by keeping the extreme points of CDHR or
272+
//' ix) \code{'BRDHR'} boundary sampling by keeping the extreme points of RDHR x) \code{'NUTS'} for
273+
//' NUTS Hamiltonian Monte Carlo sampler (logconcave densities) xi) \code{'HMC'} for Hamiltonian Monte Carlo (logconcave densities)
274+
//' xii) \code{'ULD'} for Underdamped Langevin Dynamics using the Randomized Midpoint Method (logconcave densities)
275+
//' xiii) \code{'ExactHMC'} for exact Hamiltonian Monte Carlo with reflections (spherical Gaussian or exponential distribution).
276+
//' The default walk is \code{'aBiW'} for the uniform distribution, \code{'CDHR'} for the Gaussian distribution and H-polytopes
277+
//' and \code{'BiW'} or \code{'RDHR'} for the same distributions and V-polytopes and zonotopes. \code{'NUTS'} is the default sampler for logconcave densities.}
248278
//' \item{\code{walk_length} }{ The number of the steps per generated point for the random walk. The default value is \eqn{1}.}
249279
//' \item{\code{nburns} }{ The number of points to burn before start sampling. The default value is \eqn{1}.}
250280
//' \item{\code{starting_point} }{ A \eqn{d}-dimensional numerical vector that declares a starting point in the interior of the polytope for the random walk. The default choice is the center of the ball as that one computed by the function \code{inner_ball()}.}
@@ -410,18 +440,24 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable<Rcpp::Reference> P,
410440
Rcpp::Function negative_logprob = Rcpp::as<Rcpp::List>(distribution)["negative_logprob"];
411441
Rcpp::Function negative_logprob_gradient = Rcpp::as<Rcpp::List>(distribution)["negative_logprob_gradient"];
412442

413-
NT L_, m;
443+
NT L_ = 1, m = 1;
414444

415445
if (Rcpp::as<Rcpp::List>(distribution).containsElementNamed("L_")) {
416446
L_ = Rcpp::as<NT>(Rcpp::as<Rcpp::List>(distribution)["L_"]);
447+
if (L_ <= NT(0)) {
448+
throw Rcpp::exception("The smoothness constant must be positive");
449+
}
417450
} else {
418-
throw Rcpp::exception("The smoothness constant is absent");
451+
L_ = -1;
419452
}
420453

421454
if (Rcpp::as<Rcpp::List>(distribution).containsElementNamed("m")) {
422455
m = Rcpp::as<NT>(Rcpp::as<Rcpp::List>(distribution)["m"]);
456+
if (m <= NT(0)) {
457+
throw Rcpp::exception("The strong-convexity constant must be positive");
458+
}
423459
} else {
424-
throw Rcpp::exception("The strong-convexity constant is absent");
460+
m = -1;
425461
}
426462

427463
if (Rcpp::as<Rcpp::List>(random_walk).containsElementNamed("step_size")) {
@@ -443,7 +479,6 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable<Rcpp::Reference> P,
443479
throw Rcpp::exception("Invalid ODE solver specified. Aborting.");
444480
}
445481
} else {
446-
Rcpp::warning("Solver set to leapfrog.");
447482
solver = leapfrog;
448483
}
449484

@@ -495,6 +530,8 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable<Rcpp::Reference> P,
495530
throw Rcpp::exception("Exponential sampling is supported only for H-polytopes");
496531
}
497532
walk = exponential_hmc;
533+
} else if (logconcave) {
534+
walk = nuts;
498535
} else if (gaussian) {
499536
if (type == 1) {
500537
walk = cdhr;
@@ -571,7 +608,12 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable<Rcpp::Reference> P,
571608
}
572609
} else if (Rcpp::as<std::string>(Rcpp::as<Rcpp::List>(random_walk)["walk"]).compare(std::string("HMC")) == 0) {
573610
if (!logconcave) throw Rcpp::exception("HMC is not supported for non first-order sampling");
611+
if (F->params.L < 0) throw Rcpp::exception("The smoothness constant is absent");
612+
if (F->params.m < 0) throw Rcpp::exception("The strong-convexity constant is absent");
574613
walk = hmc;
614+
} else if (Rcpp::as<std::string>(Rcpp::as<Rcpp::List>(random_walk)["walk"]).compare(std::string("NUTS")) == 0) {
615+
if (!logconcave) throw Rcpp::exception("NUTS is not supported for non first-order sampling");
616+
walk = nuts;
575617
} else if (Rcpp::as<std::string>(Rcpp::as<Rcpp::List>(random_walk)["walk"]).compare(std::string("ULD")) == 0) {
576618
if (!logconcave) throw Rcpp::exception("ULD is not supported for non first-order sampling");
577619
walk = uld;
@@ -703,7 +745,7 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable<Rcpp::Reference> P,
703745
if (numpoints % 2 == 1 && (walk == brdhr || walk == bcdhr)) numpoints--;
704746
MT RetMat(dim, numpoints);
705747
unsigned int jj = 0;
706-
748+
707749
for (typename std::list<Point>::iterator rpit = randPoints.begin(); rpit!=randPoints.end(); rpit++, jj++) {
708750
if (gaussian) {
709751
RetMat.col(jj) = (*rpit).getCoefficients() + mode.getCoefficients();

examples/logconcave/CMakeLists.txt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ endif(COMMAND cmake_policy)
2020

2121
option(DISABLE_NLP_ORACLES "Disable non-linear oracles (used in collocation)" ON)
2222
option(BUILTIN_EIGEN "Use eigen from ../external" OFF)
23-
option(USE_MKL "Use MKL library to build eigen" ON)
23+
option(USE_MKL "Use MKL library to build eigen" OFF)
2424

2525
if(DISABLE_NLP_ORACLES)
2626
add_definitions(-DDISABLE_NLP_ORACLES)
@@ -73,16 +73,24 @@ else ()
7373
include_directories(BEFORE /usr/include/eigen3)
7474
endif(BUILTIN_EIGEN)
7575

76+
find_library(BLAS NAMES libblas.so libblas.dylib PATHS /usr/local/Cellar/lapack/3.9.1_1/lib /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu /usr/local/Cellar/openblas/0.3.15_1/lib /usr/lib)
77+
7678
if (USE_MKL)
77-
find_library(BLAS NAME libblas.so PATHS /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu)
79+
find_library(BLAS NAMES libblas.so libblas.dylib PATHS /usr/local/Cellar/lapack/3.9.1_1/lib /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu /usr/local/Cellar/openblas/0.3.15_1/lib /usr/lib)
80+
find_library(GFORTRAN NAME libgfortran.dylib PATHS /usr/local/Cellar/gcc/10.2.0_4/lib/gcc/10)
81+
find_library(LAPACK NAME liblapack.dylib PATHS /usr/lib)
82+
find_library(OPENMP NAME libiomp5.dylib PATHS /opt/intel/oneapi/compiler/2021.1.1/mac/compiler/lib)
83+
7884
include_directories (BEFORE ${MKLROOT}/include)
79-
set(PROJECT_LIBS ${BLAS_LIBRARIES})
85+
set(PROJECT_LIBS ${BLAS_LIBRARIES} ${LAPACK_LIBRARIES} ${GFORTRAN_LIBRARIES})
8086
set(MKL_LINK "-L${MKLROOT}/lib -Wl,-rpath,${MKLROOT}/lib -lmkl_intel_ilp64 -lmkl_sequential -lmkl_core -lpthread -lm -ldl")
8187
add_definitions(-DEIGEN_USE_MKL_ALL)
88+
else()
89+
set(MKL_LINK "")
8290
endif(USE_MKL)
8391

8492
# Find lpsolve library
85-
find_library(LP_SOLVE NAMES liblpsolve55.so PATHS /usr/lib/lp_solve)
93+
find_library(LP_SOLVE NAMES liblpsolve55.so liblpsolve55.dylib PATHS /usr/lib/lp_solve /usr/local/lib)
8694

8795
if (NOT LP_SOLVE)
8896
message(FATAL_ERROR "This program requires the lp_solve library, and will not be compiled.")
@@ -145,6 +153,6 @@ else ()
145153
TARGET_LINK_LIBRARIES(simple_hmc ${LP_SOLVE} ${BLAS} ${MKL_LINK} )
146154
TARGET_LINK_LIBRARIES(exponential_exact_hmc ${LP_SOLVE} ${BLAS} ${MKL_LINK} )
147155
TARGET_LINK_LIBRARIES(gaussian_exact_hmc ${LP_SOLVE} ${BLAS} ${MKL_LINK} )
148-
156+
149157

150158
endif()

external/cmake-files/LPSolve.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function(GetLPSolve)
2626
message(STATUS "lp_solve library found: ${LP_SOLVE_DIR}")
2727

2828
endif()
29-
29+
3030
include_directories(${LP_SOLVE_DIR})
3131

3232
endfunction()

include/cartesian_geom/point.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class point
8181
void set_dimension(const unsigned int dim)
8282
{
8383
d = dim;
84+
coeffs.setZero(d);
8485
}
8586

8687
void set_coord(const unsigned int i, FT coord)

include/ode_solvers/leapfrog.hpp

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ struct LeapfrogODESolver {
5656
pts xs;
5757
pts xs_prev;
5858

59+
Point grad_x;
60+
5961
MT _AA;
6062

6163
std::pair<NT, int> pbpair;
@@ -69,11 +71,11 @@ struct LeapfrogODESolver {
6971
eta(step), eta0(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state), adaptive(adaptive_) {
7072
dim = xs[0].dimension();
7173
_update_parameters = update_parameters();
74+
grad_x.set_dimension(dim);
7275
initialize();
7376
};
7477

7578

76-
7779
void initialize() {
7880
for (unsigned int i = 0; i < xs.size(); i++) {
7981
VT ar, av;
@@ -88,7 +90,6 @@ struct LeapfrogODESolver {
8890
Av.push_back(av);
8991
lambda_prev.push_back(NT(0));
9092
}
91-
//step();
9293
}
9394

9495
void disable_adaptive() {
@@ -101,27 +102,28 @@ struct LeapfrogODESolver {
101102

102103
void step(int k, bool accepted) {
103104
num_steps++;
104-
105105
if (adaptive) eta = (eta0 * num_steps) / (num_steps + num_reflections);
106106

107107
xs_prev = xs;
108108
unsigned int x_index, v_index, it;
109109
t += eta;
110+
Point y;
110111
for (unsigned int i = 1; i < xs.size(); i += 2) {
111-
//pbpair.second = -1;
112+
112113
x_index = i - 1;
113114
v_index = i;
114115

115116
// v' <- v + eta / 2 F(x)
116-
Point z = F(v_index, xs_prev, t);
117-
z = (eta / 2) * z;
118-
xs[v_index] = xs[v_index] + z;
117+
if (k == 0 && !accepted) {
118+
grad_x = F(v_index, xs_prev, t);
119+
}
120+
xs[v_index] += (eta / 2) * grad_x;
119121

120122
// x <- x + eta v'
121-
Point y = xs[v_index];
123+
y = xs[v_index];
122124

123125
if (Ks[x_index] == NULL) {
124-
xs[x_index] = xs_prev[x_index] + eta*y;
126+
xs[x_index] += eta*y;
125127
}
126128
else {
127129
// Find intersection (assuming a line trajectory) between x and y
@@ -173,10 +175,8 @@ struct LeapfrogODESolver {
173175
}
174176

175177
// tilde v <- v + eta / 2 F(tilde x)
176-
z = F(v_index, xs, t);
177-
z = (eta / 2) * z;
178-
xs[v_index] = xs[v_index] + z;
179-
178+
grad_x = F(v_index, xs, t);
179+
xs[v_index] += (eta / 2) * grad_x;
180180
}
181181

182182
}
@@ -202,6 +202,19 @@ struct LeapfrogODESolver {
202202
xs[index] = p;
203203
}
204204

205+
NT get_eta() {
206+
return eta;
207+
}
208+
209+
void set_eta(NT &eta_) {
210+
eta = eta_;
211+
eta0 = eta_;
212+
}
213+
214+
bounds get_bounds() {
215+
return Ks;
216+
}
217+
205218
};
206219

207220

0 commit comments

Comments
 (0)