diff --git a/R-proj/NAMESPACE b/R-proj/NAMESPACE index d445f3bb6..3821b4c1b 100644 --- a/R-proj/NAMESPACE +++ b/R-proj/NAMESPACE @@ -40,4 +40,4 @@ importFrom("stats","cov") importFrom("utils","read.csv") importFrom(Rcpp,evalCpp) importFrom(Rcpp,loadModule) -useDynLib(volesti, .registration=TRUE) +useDynLib(volesti, .registration=TRUE) \ No newline at end of file diff --git a/R-proj/R/RcppExports.R b/R-proj/R/RcppExports.R index 3ced1a919..1e148d52e 100644 --- a/R-proj/R/RcppExports.R +++ b/R-proj/R/RcppExports.R @@ -67,6 +67,7 @@ direct_sampling <- function(body, n, seed = NULL) { #' Gelman-Rubin and Brooks-Gelman Potential Scale Reduction Factor (PSRF) for each marginal #' #' @param samples A matrix that contans column-wise the sampled points from a geometric random walk. +#' @param method A string to reauest diagnostic: (i) \code{'normal'} for psrf of Gelman-Rubin and (ii) \code{'interval'} for psrf of Brooks-Gelman. #' #' @references \cite{Gelman, A. and Rubin, D. B., #' \dQuote{Inference from iterative simulation using multiple sequences,} \emph{Statistical Science,} 1992.} diff --git a/R-proj/src/direct_sampling.cpp b/R-proj/src/direct_sampling.cpp index 75402a56c..c9695568f 100644 --- a/R-proj/src/direct_sampling.cpp +++ b/R-proj/src/direct_sampling.cpp @@ -19,7 +19,6 @@ #include "volume/volume_sequence_of_balls.hpp" #include "sampling/simplex.hpp" - //' Sample perfect uniformly distributed points from well known convex bodies: (a) the unit simplex, (b) the canonical simplex, (c) the boundary of a hypersphere or (d) the interior of a hypersphere. //' //' The \eqn{d}-dimensional unit simplex is the set of points \eqn{\vec{x}\in \R^d}, s.t.: \eqn{\sum_i x_i\leq 1}, \eqn{x_i\geq 0}. The \eqn{d}-dimensional canonical simplex is the set of points \eqn{\vec{x}\in \R^d}, s.t.: \eqn{\sum_i x_i = 1}, \eqn{x_i\geq 0}. diff --git a/R-proj/src/frustum_of_simplex.cpp b/R-proj/src/frustum_of_simplex.cpp index 5daeade61..6b191079e 100644 --- a/R-proj/src/frustum_of_simplex.cpp +++ b/R-proj/src/frustum_of_simplex.cpp @@ -3,7 +3,6 @@ // Copyright (c) 2012-2018 Vissarion Fisikopoulos // Copyright (c) 2018 Apostolos Chalkis - #include #include #include "volume/exact_vols.h" @@ -39,5 +38,4 @@ double frustum_of_simplex(Rcpp::NumericVector a, double z0){ std::vector hyp = Rcpp::as >(a); return vol_Ali(hyp, -z0, dim); - } diff --git a/R-proj/src/geweke.cpp b/R-proj/src/geweke.cpp index 39bc17a6d..4c41b227e 100644 --- a/R-proj/src/geweke.cpp +++ b/R-proj/src/geweke.cpp @@ -17,7 +17,6 @@ #include #include "diagnostics/geweke.hpp" - //' Geweke's MCMC diagnostic //' //' @param samples A matrix that contans column-wise the sampled points from a geometric random walk. diff --git a/R-proj/src/inner_ball.cpp b/R-proj/src/inner_ball.cpp index ee79b0f77..8472ad3b8 100644 --- a/R-proj/src/inner_ball.cpp +++ b/R-proj/src/inner_ball.cpp @@ -1,8 +1,6 @@ - // Copyright (c) 2012-2018 Vissarion Fisikopoulos // Copyright (c) 2018 Apostolos Chalkis - #include #include #include @@ -97,5 +95,4 @@ Rcpp::NumericVector inner_ball(Rcpp::Reference P, vec[n] = InnerBall.second; return vec; - } diff --git a/R-proj/src/polytopes_modules.cpp b/R-proj/src/polytopes_modules.cpp index b9932aa45..88c036988 100644 --- a/R-proj/src/polytopes_modules.cpp +++ b/R-proj/src/polytopes_modules.cpp @@ -6,7 +6,6 @@ #include - class Hpolytope { public: Hpolytope() {} @@ -61,11 +60,9 @@ class VPinterVP { int type; }; - RCPP_MODULE(polytopes){ using namespace Rcpp ; - //' An exposed class to represent a H-polytope //' //' @description A H-polytope is a convex polytope defined by a set of linear inequalities or equivalently a \eqn{d}-dimensional H-polytope with \eqn{m} facets is defined by a \eqn{m\times d} matrix A and a \eqn{m}-dimensional vector b, s.t.: \eqn{Ax\leq b}. @@ -100,7 +97,6 @@ RCPP_MODULE(polytopes){ .field( "dimension", &Hpolytope::dimension ) .field( "type", &Hpolytope::type ); - //' An exposed C++ class to represent a V-polytope //' //' @description A V-polytope is a convex polytope defined by the set of its vertices. @@ -126,7 +122,6 @@ RCPP_MODULE(polytopes){ .field( "dimension", &Vpolytope::dimension ) .field( "type", &Vpolytope::type ); - //' An exposed C++ class to represent a zonotope //' //' @description A zonotope is a convex polytope defined by the Minkowski sum of \eqn{m} \eqn{d}-dimensional segments. @@ -152,7 +147,6 @@ RCPP_MODULE(polytopes){ .field( "dimension", &Zonotope::dimension ) .field( "type", &Zonotope::type ); - //' An exposed C++ class to represent an intersection of two V-polytopes //' //' @description An intersection of two V-polytopes is defined by the intersection of the two coresponding convex hulls. diff --git a/R-proj/src/rotating.cpp b/R-proj/src/rotating.cpp index e65516f86..607c00921 100644 --- a/R-proj/src/rotating.cpp +++ b/R-proj/src/rotating.cpp @@ -99,5 +99,4 @@ Rcpp::NumericMatrix rotating (Rcpp::Reference P, Rcpp::Nullable(Mat).rows()+n); res << Rcpp::as(Mat).transpose(), TransorfMat; return Rcpp::wrap(res); - } diff --git a/R-proj/src/rounding.cpp b/R-proj/src/rounding.cpp index e9330d088..175ac84af 100644 --- a/R-proj/src/rounding.cpp +++ b/R-proj/src/rounding.cpp @@ -139,5 +139,4 @@ Rcpp::List rounding (Rcpp::Reference P, return Rcpp::List::create(Rcpp::Named("Mat") = Mat, Rcpp::Named("T") = Rcpp::wrap(std::get<0>(round_res)), Rcpp::Named("shift") = Rcpp::wrap(std::get<1>(round_res)), Rcpp::Named("round_value") = std::get<2>(round_res)); - } diff --git a/R-proj/src/sample_points.cpp b/R-proj/src/sample_points.cpp index 827963917..f1cf654c3 100644 --- a/R-proj/src/sample_points.cpp +++ b/R-proj/src/sample_points.cpp @@ -24,7 +24,6 @@ #include "ode_solvers/ode_solvers.hpp" #include "ode_solvers/oracle_functors_rcpp.hpp" - enum random_walks { ball_walk, rdhr, @@ -714,5 +713,4 @@ Rcpp::NumericMatrix sample_points(Rcpp::Nullable P, } return Rcpp::wrap(RetMat); - } diff --git a/R-proj/src/spectrahedron_module.cpp b/R-proj/src/spectrahedron_module.cpp index 12c216750..8dedd2dc1 100644 --- a/R-proj/src/spectrahedron_module.cpp +++ b/R-proj/src/spectrahedron_module.cpp @@ -20,7 +20,6 @@ class Spectrahedron { Spectrahedron(Rcpp::List _matrices) : matrices(_matrices) {} }; - RCPP_MODULE(spectrahedron){ using namespace Rcpp ; diff --git a/R-proj/src/volume.cpp b/R-proj/src/volume.cpp index 1cc5689a7..0a47210aa 100644 --- a/R-proj/src/volume.cpp +++ b/R-proj/src/volume.cpp @@ -405,4 +405,3 @@ Rcpp::List volume (Rcpp::Reference P, return Rcpp::List::create(Rcpp::Named("log_volume") = pair_vol.first, Rcpp::Named("volume") = pair_vol.second); } - diff --git a/R-proj/src/zonotope_approximation.cpp b/R-proj/src/zonotope_approximation.cpp index e91e1010c..237ec96e1 100644 --- a/R-proj/src/zonotope_approximation.cpp +++ b/R-proj/src/zonotope_approximation.cpp @@ -99,5 +99,4 @@ Rcpp::List zono_approx (Rcpp::Reference Z, } return Rcpp::List::create(Rcpp::Named("Mat") = Rcpp::wrap(Mat), Rcpp::Named("fit_ratio") = ratio); - } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9fbd761ef..2b23cb333 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,6 @@ # VolEsti (volume computation and sampling library) -# Copyright (c) 2012-2018 Vissarion Fisikopoulos -# Copyright (c) 2018 Apostolos Chalkis +# Copyright (c) 2012-2021 Vissarion Fisikopoulos +# Copyright (c) 2018-2021 Apostolos Chalkis # Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. # Licensed under GNU LGPL.3, see LICENCE file @@ -112,6 +112,7 @@ else () add_subdirectory(logconcave) #add_subdirectory(spectrahedra) add_subdirectory(hpolytope-volume) + add_subdirectory(mmcs_method) add_subdirectory(count-linear-extensions-using-hpolytope) add_subdirectory(ellipsoid-sampling) add_subdirectory(order-polytope-basics) diff --git a/examples/mmcs_method/Boost.cmake b/examples/mmcs_method/Boost.cmake new file mode 100644 index 000000000..1a4dfc952 --- /dev/null +++ b/examples/mmcs_method/Boost.cmake @@ -0,0 +1,35 @@ +function(GetBoost) + + find_path(BOOST_DIR NAMES boost PATHS ../external/) + + if (NOT BOOST_DIR) + + set(BOOST_URL "https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2" CACHE STRING "Boost download URL") + set(BOOST_URL_SHA256 "f0397ba6e982c4450f27bf32a2a83292aba035b827a5623a14636ea583318c41" CACHE STRING "Boost download URL SHA256 checksum") + + include(FetchContent) + FetchContent_Declare( + Boost + URL ${BOOST_URL} + URL_HASH SHA256=${BOOST_URL_SHA256} + ) + FetchContent_GetProperties(Boost) + + if(NOT Boost_POPULATED) + message(STATUS "Fetching Boost") + FetchContent_Populate(Boost) + message(STATUS "Fetching Boost - done") + set(BOOST_DIR ${boost_SOURCE_DIR}) + endif() + + message(STATUS "Using downloaded Boost library at ${BOOST_DIR}") + + else () + message(STATUS "Boost Library found: ${BOOST_DIR}") + + endif() + + include_directories(${BOOST_DIR}) + include_directories(${BOOST_DIR}/boost) + +endfunction() diff --git a/examples/mmcs_method/CMakeLists.txt b/examples/mmcs_method/CMakeLists.txt new file mode 100644 index 000000000..11792f1fa --- /dev/null +++ b/examples/mmcs_method/CMakeLists.txt @@ -0,0 +1,124 @@ +# VolEsti (volume computation and sampling library) +# Copyright (c) 2012-2018 Vissarion Fisikopoulos +# Copyright (c) 2018 Apostolos Chalkis +# Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. +# Licensed under GNU LGPL.3, see LICENCE file + + +project( VolEsti ) + + +CMAKE_MINIMUM_REQUIRED(VERSION 2.4.5) + +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) + +if(COMMAND cmake_policy) + cmake_policy(SET CMP0003 NEW) +endif(COMMAND cmake_policy) + + +option(DISABLE_NLP_ORACLES "Disable non-linear oracles (used in collocation)" ON) + +if(DISABLE_NLP_ORACLES) + add_definitions(-DDISABLE_NLP_ORACLES) +else() + find_library(IFOPT NAMES libifopt_core.so PATHS /usr/local/lib) + find_library(IFOPT_IPOPT NAMES libifopt_ipopt.so PATHS /usr/local/lib) + find_library(GMP NAMES libgmp.so PATHS /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu) + find_library(MPSOLVE NAMES libmps.so PATHS /usr/local/lib) + find_library(PTHREAD NAMES libpthread.so PATHS /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu) + find_library(FFTW3 NAMES libfftw3.so.3 PATHS /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu) + + if (NOT IFOPT) + + message(FATAL_ERROR "This program requires the ifopt library, and will not be compiled.") + + elseif (NOT GMP) + + message(FATAL_ERROR "This program requires the gmp library, and will not be compiled.") + + elseif (NOT MPSOLVE) + + message(FATAL_ERROR "This program requires the mpsolve library, and will not be compiled.") + + elseif (NOT FFTW3) + + message(FATAL_ERROR "This program requires the fftw3 library, and will not be compiled.") + + else() + message(STATUS "Library ifopt found: ${IFOPT}") + message(STATUS "Library gmp found: ${GMP}") + message(STATUS "Library mpsolve found: ${MPSOLVE}") + message(STATUS "Library fftw3 found:" ${FFTW3}) + + endif(NOT IFOPT) + +endif(DISABLE_NLP_ORACLES) + +include("Eigen.cmake") +GetEigen() + +include("Boost.cmake") +GetBoost() + +# Find lpsolve library +find_library(LP_SOLVE NAMES liblpsolve55.so PATHS /usr/lib/lp_solve) + +if (NOT LP_SOLVE) + message(FATAL_ERROR "This program requires the lp_solve library, and will not be compiled.") +else () + message(STATUS "Library lp_solve found: ${LP_SOLVE}") + + set(CMAKE_EXPORT_COMPILE_COMMANDS "ON") + + include_directories (BEFORE ../../external/Eigen) + include_directories (BEFORE ../../external) + include_directories (BEFORE ../../external/minimum_ellipsoid) + #include_directories (BEFORE ../../include/cartesian_geom) + #include_directories (BEFORE ../../include/convex_bodies) + include_directories (BEFORE ../../external/LPsolve_src/run_headers) + include_directories (BEFORE ../../external/boost) + #include_directories (BEFORE BOOST) + include_directories (BEFORE ../../include/generators) + include_directories (BEFORE ../../include/volume) + include_directories (BEFORE ../../include) + include_directories (BEFORE ../../include/lp_oracles) + include_directories (BEFORE ../../include/nlp_oracles) + include_directories (BEFORE ../../include/convex_bodies) + include_directories (BEFORE ../../include/random_walks) + include_directories (BEFORE ../../include/annealing) + include_directories (BEFORE ../../include/ode_solvers) + include_directories (BEFORE ../../include/root_finders) + include_directories (BEFORE ../../include/samplers) + include_directories (BEFORE ../../include/misc) + include_directories (BEFORE ../../include/optimization) + + # for Eigen + if (${CMAKE_VERSION} VERSION_LESS "3.12.0") + add_compile_options(-D "EIGEN_NO_DEBUG") + else () + add_compile_definitions("EIGEN_NO_DEBUG") + endif () + + + add_definitions(${CMAKE_CXX_FLAGS} "-std=c++11") # enable C++11 standard + add_definitions(${CMAKE_CXX_FLAGS} "-O3") # optimization of the compiler + #add_definitions(${CXX_COVERAGE_COMPILE_FLAGS} "-lgsl") + add_definitions(${CXX_COVERAGE_COMPILE_FLAGS} "-lm") + add_definitions(${CXX_COVERAGE_COMPILE_FLAGS} "-ldl") + add_definitions(${CXX_COVERAGE_COMPILE_FLAGS} "-DBOOST_NO_AUTO_PTR") + #add_definitions(${CXX_COVERAGE_COMPILE_FLAGS} "-lgslcblas") + #add_definitions( "-O3 -lgsl -lm -ldl -lgslcblas" ) + + find_package(OpenMP REQUIRED) + + add_executable (skinny_cube_10_dim skinny_cube_10_dim.cpp) + add_executable (random_hpoly_50_dim random_hpoly_50_dim.cpp) + add_executable (random_hpoly_50_dim_parallel random_hpoly_50_dim_parallel.cpp) + + TARGET_LINK_LIBRARIES(skinny_cube_10_dim ${LP_SOLVE}) + TARGET_LINK_LIBRARIES(random_hpoly_50_dim ${LP_SOLVE}) + TARGET_LINK_LIBRARIES(random_hpoly_50_dim_parallel ${LP_SOLVE} OpenMP::OpenMP_CXX) + + +endif() diff --git a/examples/mmcs_method/Eigen.cmake b/examples/mmcs_method/Eigen.cmake new file mode 100644 index 000000000..5ebc7e599 --- /dev/null +++ b/examples/mmcs_method/Eigen.cmake @@ -0,0 +1,30 @@ +function(GetEigen) + find_path(EIGEN_DIR NAMES Eigen PATHS ../../external) + + if (NOT EIGEN_DIR) + include(FetchContent) + FetchContent_Declare( + eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG 3.3.9 + ) + + FetchContent_GetProperties(eigen) + + if(NOT eigen_POPULATED) + message(STATUS "Eigen library not found locally, downloading it.") + FetchContent_Populate(eigen) + endif() + + set(EIGEN_DIR ${eigen_SOURCE_DIR}) + message(STATUS "Using downloaded Eigen library at: ${EIGEN_DIR}") + + else () + + message(STATUS "Eigen Library found: ${EIGEN_DIR}") + + endif() + + include_directories(${EIGEN_DIR}) + +endfunction() \ No newline at end of file diff --git a/examples/mmcs_method/random_hpoly_50_dim.cpp b/examples/mmcs_method/random_hpoly_50_dim.cpp new file mode 100644 index 000000000..90cae4fa3 --- /dev/null +++ b/examples/mmcs_method/random_hpoly_50_dim.cpp @@ -0,0 +1,170 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis + +#include "Eigen/Eigen" + +#include +#include +#include +#include +#include +#include "random_walks/random_walks.hpp" +#include "volume/volume_sequence_of_balls.hpp" +#include "volume/volume_cooling_gaussians.hpp" +#include "sampling/mmcs.hpp" +#include "generators/h_polytopes_generator.h" +#include "diagnostics/multivariate_psrf.hpp" +#include "diagnostics/univariate_psrf.hpp" +#include "diagnostics/ess_window_updater.hpp" + + +template +void run_main() +{ + typedef Cartesian Kernel; + typedef BoostRandomNumberGenerator RNGType; + typedef boost::mt19937 PolyRNGType; + typedef typename Kernel::Point Point; + typedef HPolytope Hpolytope; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + + int n = 50; + + RNGType rng(n); + Hpolytope P = random_hpoly(n, 4*n, 127); // we fix the example polytope, seed = 127 + std::list randPoints; + + MT T = MT::Identity(n, n); + VT T_shift = VT::Zero(n); + + unsigned int round_it = 1, num_rounding_steps = 20*n, + walk_length = 1, num_its = 20, Neff = 4000, total_neff = 0, phase = 0, + window = 100, max_num_samples = 100 * n, total_samples, nburns, total_number_of_samples_in_P0 = 0; + NT max_s, s_cutoff = 3.0, L; + bool complete = false, request_rounding = true, + rounding_completed = false; + bool req_round_temp = request_rounding; + + std::pair InnerBall; + + MT S; + + std::cout << "target effective sample size = " << Neff << "\n" << std::endl; + + while(true) + { + phase++; + if (request_rounding && rounding_completed) + { + req_round_temp = false; + } + + if (req_round_temp) + { + nburns = num_rounding_steps / window + 1; + } + else + { + nburns = max_num_samples / window + 1; + } + + InnerBall = P.ComputeInnerBall(); + L = NT(6) * std::sqrt(NT(n)) * InnerBall.second; + AcceleratedBilliardWalk WalkType(L); + + unsigned int Neff_sampled; + MT TotalRandPoints; + complete = perform_mmcs_step(P, rng, walk_length, Neff, max_num_samples, window, + Neff_sampled, total_samples, num_rounding_steps, TotalRandPoints, + InnerBall.first, nburns, req_round_temp, WalkType); + + Neff -= Neff_sampled; + std::cout << "phase " << phase << ": number of correlated samples = " << total_samples << ", effective sample size = " << Neff_sampled; + total_neff += Neff_sampled; + Neff_sampled = 0; + + MT Samples = TotalRandPoints.transpose(); //do not copy TODO! + for (int i = 0; i < total_samples; i++) + { + Samples.col(i) = T * Samples.col(i) + T_shift; + } + + S.conservativeResize(P.dimension(), total_number_of_samples_in_P0 + total_samples); + S.block(0, total_number_of_samples_in_P0, P.dimension(), total_samples) = Samples.block(0, 0, P.dimension(), total_samples); + total_number_of_samples_in_P0 += total_samples; + if (!complete) + { + if (request_rounding && !rounding_completed) + { + VT shift(n), s(n); + MT V(n,n), S(n,n), round_mat; + for (int i = 0; i < P.dimension(); ++i) + { + shift(i) = TotalRandPoints.col(i).mean(); + } + + for (int i = 0; i < total_samples; ++i) + { + TotalRandPoints.row(i) = TotalRandPoints.row(i) - shift.transpose(); + } + + Eigen::BDCSVD svd(TotalRandPoints, Eigen::ComputeFullV); + s = svd.singularValues() / svd.singularValues().minCoeff(); + + if (s.maxCoeff() >= 2.0) + { + for (int i = 0; i < s.size(); ++i) + { + if (s(i) < 2.0) + { + s(i) = 1.0; + } + } + V = svd.matrixV(); + } + else + { + s = VT::Ones(P.dimension()); + V = MT::Identity(P.dimension(), P.dimension()); + } + max_s = s.maxCoeff(); + S = s.asDiagonal(); + round_mat = V * S; + + round_it++; + P.shift(shift); + P.linear_transformIt(round_mat); + T_shift += T * shift; + T = T * round_mat; + + std::cout << ", ratio of the maximum singilar value over the minimum singular value = " << max_s << std::endl; + + if (max_s <= s_cutoff || round_it > num_its) + { + rounding_completed = true; + } + } + else + { + std::cout<<"\n"; + } + } + else + { + std::cout<<"\n\n"; + break; + } + } + + std::cerr << "sum of effective sample sizes: " << total_neff << std::endl; + std::cerr << "multivariate PSRF: " << multivariate_psrf(S) << std::endl; + std::cerr << "maximum marginal PSRF: " << univariate_psrf(S).maxCoeff() << std::endl; +} + +int main() { + run_main(); + return 0; +} diff --git a/examples/mmcs_method/random_hpoly_50_dim_parallel.cpp b/examples/mmcs_method/random_hpoly_50_dim_parallel.cpp new file mode 100644 index 000000000..747e4bd5b --- /dev/null +++ b/examples/mmcs_method/random_hpoly_50_dim_parallel.cpp @@ -0,0 +1,169 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis + +#include "Eigen/Eigen" + +#include +#include +#include +#include +#include +#include "random_walks/random_walks.hpp" +#include "volume/volume_sequence_of_balls.hpp" +#include "volume/volume_cooling_gaussians.hpp" +#include "sampling/parallel_mmcs.hpp" +#include "generators/h_polytopes_generator.h" +#include "diagnostics/multivariate_psrf.hpp" +#include "diagnostics/univariate_psrf.hpp" +#include "diagnostics/ess_window_updater.hpp" + + +template +void run_main() +{ + typedef Cartesian Kernel; + typedef BoostRandomNumberGenerator RNGType; + typedef boost::mt19937 PolyRNGType; + typedef typename Kernel::Point Point; + typedef HPolytope Hpolytope; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + + int n = 50; + + RNGType rng(n); + Hpolytope P = random_hpoly(n, 4*n, 127); // we fix the example polytope, seed = 127 + std::list randPoints; + + MT T = MT::Identity(n, n); + VT T_shift = VT::Zero(n); + + unsigned int round_it = 1, num_rounding_steps = 20*n, + walk_length = 1, num_its = 20, Neff = 4000, total_neff = 0, phase = 0, + window = 100, max_num_samples = 100 * n, total_samples, nburns, total_number_of_samples_in_P0 = 0; + NT max_s, s_cutoff = 3.0, L; + bool complete = false, request_rounding = true, + rounding_completed = false; + bool req_round_temp = request_rounding; + + std::pair InnerBall; + + MT S; + + std::cout << "target effective sample size = " << Neff << "\n" << std::endl; + + while(true) + { + phase++; + if (request_rounding && rounding_completed) + { + req_round_temp = false; + } + + if (req_round_temp) + { + nburns = num_rounding_steps / window + 1; + } + else + { + nburns = max_num_samples / window + 1; + } + + InnerBall = P.ComputeInnerBall(); + L = NT(6) * std::sqrt(NT(n)) * InnerBall.second; + //AcceleratedBilliardWalk WalkType(L); + + unsigned int Neff_sampled, num_threads = 2; + MT TotalRandPoints; + complete = perform_parallel_mmcs_step(P, rng, walk_length, Neff, max_num_samples, window, + Neff_sampled, total_samples, num_rounding_steps, TotalRandPoints, + InnerBall.first, nburns, num_threads, req_round_temp, L); + Neff -= Neff_sampled; + std::cout << "phase " << phase << ": number of correlated samples = " << total_samples << ", effective sample size = " << Neff_sampled; + total_neff += Neff_sampled; + Neff_sampled = 0; + + MT Samples = TotalRandPoints.transpose(); //do not copy TODO! + for (int i = 0; i < total_samples; i++) + { + Samples.col(i) = T * Samples.col(i) + T_shift; + } + + S.conservativeResize(P.dimension(), total_number_of_samples_in_P0 + total_samples); + S.block(0, total_number_of_samples_in_P0, P.dimension(), total_samples) = Samples.block(0, 0, P.dimension(), total_samples); + total_number_of_samples_in_P0 += total_samples; + if (!complete) + { + if (request_rounding && !rounding_completed) + { + VT shift(n), s(n); + MT V(n,n), S(n,n), round_mat; + for (int i = 0; i < P.dimension(); ++i) + { + shift(i) = TotalRandPoints.col(i).mean(); + } + + for (int i = 0; i < total_samples; ++i) + { + TotalRandPoints.row(i) = TotalRandPoints.row(i) - shift.transpose(); + } + + Eigen::BDCSVD svd(TotalRandPoints, Eigen::ComputeFullV); + s = svd.singularValues() / svd.singularValues().minCoeff(); + + if (s.maxCoeff() >= 2.0) + { + for (int i = 0; i < s.size(); ++i) + { + if (s(i) < 2.0) + { + s(i) = 1.0; + } + } + V = svd.matrixV(); + } + else + { + s = VT::Ones(P.dimension()); + V = MT::Identity(P.dimension(), P.dimension()); + } + max_s = s.maxCoeff(); + S = s.asDiagonal(); + round_mat = V * S; + + round_it++; + P.shift(shift); + P.linear_transformIt(round_mat); + T_shift += T * shift; + T = T * round_mat; + + std::cout << ", ratio of the maximum singilar value over the minimum singular value = " << max_s << std::endl; + + if (max_s <= s_cutoff || round_it > num_its) + { + rounding_completed = true; + } + } + else + { + std::cout<<"\n"; + } + } + else + { + std::cout<<"\n\n"; + break; + } + } + + std::cerr << "sum of effective sample sizes: " << total_neff << std::endl; + std::cerr << "multivariate PSRF: " << multivariate_psrf(S) << std::endl; + std::cerr << "maximum marginal PSRF: " << univariate_psrf(S).maxCoeff() << std::endl; +} + +int main() { + run_main(); + return 0; +} diff --git a/examples/mmcs_method/skinny_cube_10_dim.cpp b/examples/mmcs_method/skinny_cube_10_dim.cpp new file mode 100644 index 000000000..036b10a34 --- /dev/null +++ b/examples/mmcs_method/skinny_cube_10_dim.cpp @@ -0,0 +1,169 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis + +#include "Eigen/Eigen" + +#include +#include +#include +#include +#include +#include "random_walks/random_walks.hpp" +#include "volume/volume_sequence_of_balls.hpp" +#include "volume/volume_cooling_gaussians.hpp" +#include "sampling/mmcs.hpp" +#include "generators/known_polytope_generators.h" +#include "diagnostics/multivariate_psrf.hpp" +#include "diagnostics/univariate_psrf.hpp" +#include "diagnostics/ess_window_updater.hpp" + + +template +void run_main() +{ + typedef Cartesian Kernel; + typedef BoostRandomNumberGenerator RNGType; + typedef typename Kernel::Point Point; + typedef HPolytope Hpolytope; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + + int n = 10; + + Hpolytope P = generate_skinny_cube(n, false); + std::list randPoints; + RNGType rng(n); + + MT T = MT::Identity(n, n); + VT T_shift = VT::Zero(n); + + unsigned int round_it = 1, num_rounding_steps = 20*n, + walk_length = 1, num_its = 20, Neff = 1000, total_neff = 0, phase = 0, + window = 100, max_num_samples = 100 * n, total_samples, nburns, total_number_of_samples_in_P0 = 0; + NT max_s, s_cutoff = 3.0, L; + bool complete = false, request_rounding = true, + rounding_completed = false; + bool req_round_temp = request_rounding; + + std::pair InnerBall; + + MT S; + + std::cout << "target effective sample size = " << Neff << "\n" << std::endl; + + while(true) + { + phase++; + if (request_rounding && rounding_completed) + { + req_round_temp = false; + } + + if (req_round_temp) + { + nburns = num_rounding_steps / window + 1; + } + else + { + nburns = max_num_samples / window + 1; + } + + InnerBall = P.ComputeInnerBall(); + L = NT(6) * std::sqrt(NT(n)) * InnerBall.second; + AcceleratedBilliardWalk WalkType(L); + + unsigned int Neff_sampled; + MT TotalRandPoints; + complete = perform_mmcs_step(P, rng, walk_length, Neff, max_num_samples, window, + Neff_sampled, total_samples, num_rounding_steps, TotalRandPoints, + InnerBall.first, nburns, req_round_temp, WalkType); + + Neff -= Neff_sampled; + std::cout << "phase " << phase << ": number of correlated samples = " << total_samples << ", effective sample size = " << Neff_sampled; + total_neff += Neff_sampled; + Neff_sampled = 0; + + MT Samples = TotalRandPoints.transpose(); //do not copy TODO! + for (int i = 0; i < total_samples; i++) + { + Samples.col(i) = T * Samples.col(i) + T_shift; + } + + S.conservativeResize(P.dimension(), total_number_of_samples_in_P0 + total_samples); + S.block(0, total_number_of_samples_in_P0, P.dimension(), total_samples) = Samples.block(0, 0, P.dimension(), total_samples); + total_number_of_samples_in_P0 += total_samples; + if (!complete) + { + if (request_rounding && !rounding_completed) + { + VT shift(n), s(n); + MT V(n,n), S(n,n), round_mat; + for (int i = 0; i < P.dimension(); ++i) + { + shift(i) = TotalRandPoints.col(i).mean(); + } + + for (int i = 0; i < total_samples; ++i) + { + TotalRandPoints.row(i) = TotalRandPoints.row(i) - shift.transpose(); + } + + Eigen::BDCSVD svd(TotalRandPoints, Eigen::ComputeFullV); + s = svd.singularValues() / svd.singularValues().minCoeff(); + + if (s.maxCoeff() >= 2.0) + { + for (int i = 0; i < s.size(); ++i) + { + if (s(i) < 2.0) + { + s(i) = 1.0; + } + } + V = svd.matrixV(); + } + else + { + s = VT::Ones(P.dimension()); + V = MT::Identity(P.dimension(), P.dimension()); + } + max_s = s.maxCoeff(); + S = s.asDiagonal(); + round_mat = V * S; + + round_it++; + P.shift(shift); + P.linear_transformIt(round_mat); + T_shift += T * shift; + T = T * round_mat; + + std::cout << ", ratio of the maximum singilar value over the minimum singular value = " << max_s << std::endl; + + if (max_s <= s_cutoff || round_it > num_its) + { + rounding_completed = true; + } + } + else + { + std::cout<<"\n"; + } + } + else + { + std::cout<<"\n\n"; + break; + } + } + + std::cerr << "sum of effective sample sizes: " << total_neff << std::endl; + std::cerr << "multivariate PSRF: " << multivariate_psrf(S) << std::endl; + std::cerr << "maximum marginal PSRF: " << univariate_psrf(S).maxCoeff() << std::endl; +} + +int main() { + run_main(); + return 0; +} diff --git a/include/diagnostics/ess_updater_autocovariance.hpp b/include/diagnostics/ess_updater_autocovariance.hpp new file mode 100644 index 000000000..4023a51c9 --- /dev/null +++ b/include/diagnostics/ess_updater_autocovariance.hpp @@ -0,0 +1,69 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_ESS_UPDATER_AUTOCOVARIANCE_HPP +#define DIAGNOSTICS_ESS_UPDATER_AUTOCOVARIANCE_HPP + +#include +#include +#include +#include +#include +#include + +/** + Compute autocovariance estimates for every lag for the input sequence + using the Geyer's stable estimator given in + Charles J. Geyer, Practical Markov Chain Monte Carlo, Statistical Science 1992. + + * @tparam NT number type + * @tparam VT vector type + * @param samples the sequence of correlated samples + * @param auto_cov the autocovariance estimates +*/ +template +void compute_autocovariance(VT const& samples, VT &auto_cov) +{ + const NT eps = 1e-16; + typedef Eigen::FFT EigenFFT; + typedef Eigen::Matrix, Eigen::Dynamic, 1> CVT; + EigenFFT fft; + + unsigned int N = samples.size(); + NT samples_mean = samples.mean(); + auto_cov.setZero(N); + + // compute normalized samples + VT normalized_samples(2 * N); + normalized_samples.setZero(); + normalized_samples.head(N) = samples.array() - samples_mean; + + NT variance = (normalized_samples.cwiseProduct(normalized_samples)).sum(); + variance *= (1.0 / N); + variance += eps * (samples_mean*samples_mean); + normalized_samples.head(N) = normalized_samples.head(N).array() / sqrt(variance); + + // Perform FFT on 2N points + CVT frequency(2 * N); + fft.fwd(frequency, normalized_samples); + + // Invert fft to get autocorrelation function + CVT auto_cov_tmp(2 * N); + frequency = frequency.cwiseAbs2(); + fft.inv(auto_cov_tmp, frequency); + + auto_cov = auto_cov_tmp.head(N).real().array() / N; + + boost::accumulators::accumulator_set> accumulator; + for (int i = 0; i < samples.size(); ++i) + { + accumulator(samples.coeff(i)); + } + + auto_cov = auto_cov.array() * boost::accumulators::variance(accumulator); +} + +#endif diff --git a/include/diagnostics/ess_window_updater.hpp b/include/diagnostics/ess_window_updater.hpp new file mode 100644 index 000000000..99edb5aa2 --- /dev/null +++ b/include/diagnostics/ess_window_updater.hpp @@ -0,0 +1,154 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_ESS_UPDATER_HPP +#define DIAGNOSTICS_ESS_UPDATER_HPP + +#include "ess_updater_autocovariance.hpp" + + +/** + This is a class that updates the effective sample size (ess) of a sample given a new chain + using Welford's algorithm to update the average values and the variance estimates where needed. + The chains has to be of the same length. The ess estimation exploits Geyer's stable estimator + for the autocovariance and the Geyer's conversion to a monotone sequence, given in, + + Charles J. Geyer, Practical Markov Chain Monte Carlo, Statistical Science 1992. + + * @tparam NT number type + * @tparam VT vector type + * @tparam MT matrix type +*/ +template +class ESSestimator { + +private: + unsigned int num_draws, max_s, s, d, num_chains, jj; + VT cm_mean, cm_var, cv_mean, draws, var_plus, ess, auto_cov; + NT oldM, rho_hat_odd, rho_hat_even, mean_var, M2, delta, new_elem; + MT acov_s_mean, rho_hat_s; + +public: + ESSestimator() {} + + ESSestimator(unsigned int const& _ndraws, unsigned int const& _dim) + { + num_draws = _ndraws; + d = _dim; + num_chains = 0; + + cm_mean.setZero(d); + cm_var.setZero(d); + cv_mean.setZero(d); + var_plus.setZero(d); + ess.setZero(d); + draws.setZero(num_draws); + acov_s_mean.setZero(num_draws-3, d); + rho_hat_s.setZero(num_draws, d); + } + + void update_estimator(MT const& samples) + { + num_chains++; + + for (int i = 0; i < d; i++) + { + draws = samples.row(i).transpose(); + compute_autocovariance(draws, auto_cov); + + new_elem = draws.mean(); + delta = new_elem - cm_mean.coeff(i); + cm_mean(i) += delta / NT(num_chains); + cm_var(i) += delta * (new_elem - cm_mean(i)); + + new_elem = auto_cov.coeff(0) * NT(num_draws) / (NT(num_draws) - 1.0); + delta = new_elem - cv_mean.coeff(i); + cv_mean(i) += delta / NT(num_chains); + + new_elem = auto_cov.coeff(1); + delta = new_elem - acov_s_mean.coeff(0, i); + acov_s_mean(0, i) += delta / NT(num_chains); + jj = 1; + while (jj < num_draws-4) + { + new_elem = auto_cov.coeff(jj+1); + delta = new_elem - acov_s_mean.coeff(jj, i); + acov_s_mean(jj, i) += delta / NT(num_chains); + + new_elem = auto_cov.coeff(jj+2); + delta = new_elem - acov_s_mean.coeff(jj+1, i); + acov_s_mean(jj+1, i) += delta / NT(num_chains); + + jj += 2; + } + } + } + + + void estimate_effective_sample_size() + { + rho_hat_s.setZero(num_draws, d); + + var_plus = cv_mean * (NT(num_draws) - 1.0) / NT(num_draws); + if (num_chains > 1) + { + VT cm_var_temp = cm_var * (1.0 / (NT(num_chains)-1.0)); + var_plus += cm_var_temp; + } + + for (int i = 0; i < d; i++) + { + rho_hat_even = 1.0; + rho_hat_s(0, i) = rho_hat_even; + rho_hat_odd = 1 - (cv_mean.coeff(i) - acov_s_mean.coeff(0, i)) / var_plus.coeff(i); + rho_hat_s(1, i) = rho_hat_odd; + + s = 1; + while (s < (num_draws - 4) && (rho_hat_even + rho_hat_odd) > 0) + { + rho_hat_even = 1.0 - (cv_mean.coeff(i) - acov_s_mean.coeff(s, i)) / var_plus.coeff(i); + rho_hat_odd = 1.0 - (cv_mean.coeff(i) - acov_s_mean.coeff(s+1, i)) / var_plus.coeff(i); + if ((rho_hat_even + rho_hat_odd) >= 0) + { + rho_hat_s(s + 1, i) = rho_hat_even; + rho_hat_s(s + 2, i) = rho_hat_odd; + } + s += 2; + } + + max_s = s; + // this is used in the improved estimate + if (rho_hat_even > 0) + { + rho_hat_s(max_s + 1, i) = rho_hat_even; + } + + // Convert Geyer's positive sequence into a monotone sequence + for (jj = 1; jj <= max_s - 3; jj += 2) + { + if (rho_hat_s(jj + 1, i) + rho_hat_s.coeff(jj + 2, i) > rho_hat_s.coeff(jj - 1, i) + rho_hat_s.coeff(jj, i)) + { + rho_hat_s(jj + 1, i) = (rho_hat_s.coeff(jj - 1, i) + rho_hat_s.coeff(jj, i)) / 2.0; + rho_hat_s(jj + 2, i) = rho_hat_s.coeff(jj + 1, i); + } + } + NT num_total_draws = NT(num_chains) * NT(num_draws); + NT tau_hat = -1.0 + 2.0 * rho_hat_s.col(i).head(max_s).sum() + rho_hat_s.coeff(max_s + 1, i); + ess(i) = num_total_draws / tau_hat; + } + } + + + VT get_effective_sample_size() + { + return ess; + } + +}; + + +#endif + diff --git a/include/random_walks/random_walks.hpp b/include/random_walks/random_walks.hpp index 0fc027559..898066a63 100644 --- a/include/random_walks/random_walks.hpp +++ b/include/random_walks/random_walks.hpp @@ -23,8 +23,8 @@ #include "random_walks/uniform_accelerated_billiard_walk.hpp" #include "random_walks/gaussian_hamiltonian_monte_carlo_exact_walk.hpp" #include "random_walks/exponential_hamiltonian_monte_carlo_exact_walk.hpp" +#include "random_walks/uniform_accelerated_billiard_walk_parallel.hpp" #include "random_walks/hamiltonian_monte_carlo_walk.hpp" #include "random_walks/langevin_walk.hpp" - #endif // RANDOM_WALKS_RANDOM_WALKS_HPP diff --git a/include/random_walks/uniform_accelerated_billiard_walk.hpp b/include/random_walks/uniform_accelerated_billiard_walk.hpp index 40046daa1..aeb4248b9 100644 --- a/include/random_walks/uniform_accelerated_billiard_walk.hpp +++ b/include/random_walks/uniform_accelerated_billiard_walk.hpp @@ -66,6 +66,7 @@ struct AcceleratedBilliardWalk _L = compute_diameter ::template compute(P); _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _rho = 1000 * P.dimension(); // upper bound for the number of reflections (experimental) initialize(P, p, rng); } @@ -78,6 +79,7 @@ struct AcceleratedBilliardWalk : compute_diameter ::template compute(P); _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _rho = 1000 * P.dimension(); // upper bound for the number of reflections (experimental) initialize(P, p, rng); } @@ -115,7 +117,7 @@ struct AcceleratedBilliardWalk P.compute_reflection(_v, _p, _update_parameters); it++; - while (it < 100*n) + while (it < _rho) { std::pair pbpair = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, _AA, _update_parameters); @@ -130,16 +132,91 @@ struct AcceleratedBilliardWalk P.compute_reflection(_v, _p, _update_parameters); it++; } - if (it == 100*n) _p = p0; + if (it == _rho) _p = p0; } p = _p; } + + template + < + typename GenericPolytope + > + inline void get_starting_point(GenericPolytope const& P, + Point const& center, + Point &q, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + q = GetPointInDsphere::apply(n, radius, rng); + q += center; + initialize(P, q, rng); + + apply(P, q, walk_length, rng); + } + + + template + < + typename GenericPolytope + > + inline void parameters_burnin(GenericPolytope const& P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + Point p(P.dimension()); + std::vector pointset; + pointset.push_back(center); + pointset.push_back(_p); + NT rad = NT(0), max_dist, Lmax = get_delta(), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + Point p = GetPointInDsphere::apply(P.dimension(), radius, rng); + p += center; + initialize(P, p, rng); + + apply(P, p, walk_length, rng); + max_dist = get_max_distance(pointset, p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0*rad > Lmax) { + Lmax = 2.0 * rad; + } + pointset.push_back(p); + } + + if (Lmax > _L) { + if (P.dimension() <= 2500) + { + update_delta(Lmax); + } + else{ + update_delta(2.0 * get_delta()); + } + } + pointset.clear(); + } + + + inline void update_delta(NT L) { _L = L; } + NT get_delta() + { + return _L; + } + private : template @@ -173,7 +250,7 @@ struct AcceleratedBilliardWalk T -= _lambda_prev; P.compute_reflection(_v, _p, _update_parameters); - while (it <= 100*n) + while (it <= _rho) { std::pair pbpair = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, _AA, _update_parameters); @@ -181,7 +258,7 @@ struct AcceleratedBilliardWalk _p += (T * _v); _lambda_prev = T; break; - } else if (it == 100*n) { + } else if (it == _rho) { _lambda_prev = rng.sample_urdist() * pbpair.first; _p += (_lambda_prev * _v); break; @@ -194,11 +271,31 @@ struct AcceleratedBilliardWalk } } + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0) { + if (temp_dis > rad) { + rad = temp_dis; + } + } + } + return dis; + } + double _L; Point _p; Point _v; NT _lambda_prev; MT _AA; + unsigned int _rho; update_parameters _update_parameters; typename Point::Coeff _lambdas; typename Point::Coeff _Av; diff --git a/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp b/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp new file mode 100644 index 000000000..0365ecbbc --- /dev/null +++ b/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp @@ -0,0 +1,311 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_PARALLEL_HPP +#define RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_PARALLEL_HPP + +#include "sampling/sphere.hpp" + + +// Billiard walk which accelarates each step for uniform distribution and can be used for a parallel use by threads + +struct AcceleratedBilliardWalkParallel +{ + AcceleratedBilliardWalkParallel() + {} + + + struct update_parameters + { + update_parameters() + : facet_prev(0), hit_ball(false), inner_vi_ak(0.0), ball_inner_norm(0.0) + {} + int facet_prev; + bool hit_ball; + double inner_vi_ak; + double ball_inner_norm; + }; + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + update_step_parameters = update_parameters(); + p = Point(d); + v = Point(d); + lambdas.setZero(m); + Av.setZero(m); + lambda_prev = NT(0); + } + + update_parameters update_step_parameters; + Point p; + Point v; + NT lambda_prev; + typename Point::Coeff lambdas; + typename Point::Coeff Av; + }; + + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope const& P) + { + _L = compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _p0 = Point(P.dimension()); + _rho = 1000 * P.dimension(); + } + + template + Walk(GenericPolytope const& P, NT const& L) + { + _L = L > NT(0) ? L + : compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _p0 = Point(P.dimension()); + _rho = 1000 * P.dimension(); + } + + template + < + typename GenericPolytope, + typename thread_params + > + inline void apply(GenericPolytope const& P, + thread_params ¶ms, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + const NT dl = 0.995; + int it; + + for (auto j=0u; j::apply(n, rng); + _p0 = params.p; + + it = 0; + std::pair pbpair = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, params.update_step_parameters); + if (T <= pbpair.first) + { + params.p += (T * params.v); + params.lambda_prev = T; + continue; + } + + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + + while (it < _rho) + { + std::pair pbpair + = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, _AA, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + break; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + } + if (it == _rho) params.p = _p0; + } + } + + + template + < + typename GenericPolytope, + typename thread_params + > + inline void get_starting_point(GenericPolytope const& P, + Point const& center, + thread_params ¶ms, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + params.p = GetPointInDsphere::apply(n, radius, rng); + params.p += center; + initialize(P, params, rng); + + apply(P, params, walk_length, rng); + } + + + template + < + typename GenericPolytope, + typename thread_params + > + inline void parameters_burnin(GenericPolytope const& P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng, + thread_params ¶ms) + { + std::vector pointset; + pointset.push_back(center); + + params.p = Point(P.dimension()); + NT rad = NT(0), max_dist, Lmax = get_delta(), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + params.p = GetPointInDsphere::apply(P.dimension(), radius, rng); + params.p += center; + initialize(P, params, rng); + + apply(P, params, walk_length, rng); + max_dist = get_max_distance(pointset, params.p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0*rad > Lmax) { + Lmax = 2.0 * rad; + } + pointset.push_back(params.p); + } + + if (Lmax > _L) + { + if (P.dimension() <= 2500) + { + update_delta(Lmax); + } + else{ + update_delta(2.0 * get_delta()); + } + } + pointset.clear(); + } + + + + inline void update_delta(NT L) + { + _L = L; + } + + NT get_delta() + { + return _L; + } + + private : + + template + < + typename GenericPolytope, + typename thread_params + > + inline void initialize(GenericPolytope const& P, + thread_params ¶ms, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + params.v = GetDirection::apply(n, rng); + + NT T = -std::log(rng.sample_urdist()) * _L; + int it = 0; + + std::pair pbpair + = P.line_first_positive_intersect(params.p, params.v, params.lambdas, + params.Av, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + return; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + + while (it <= _rho) + { + std::pair pbpair + = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, _AA, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + break; + } else if (it == _rho) { + params.lambda_prev = rng.sample_urdist() * pbpair.first; + params.p += (params.lambda_prev * params.v); + break; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + } + } + + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0) { + if (temp_dis > rad) { + rad = temp_dis; + } + } + } + return dis; + } + + NT _L; + MT _AA; + Point _p0; + unsigned int _rho; + }; + +}; + + +#endif + + diff --git a/include/sampling/mmcs.hpp b/include/sampling/mmcs.hpp new file mode 100644 index 000000000..dad031f95 --- /dev/null +++ b/include/sampling/mmcs.hpp @@ -0,0 +1,127 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef MMCS_HPP +#define MMCS_HPP + +#include "diagnostics/ess_window_updater.hpp" + +/** + The class implements a single step of the Multiphase Monte Carlo Sampling algorithm + given in, + + A. Chalkis, V. Fisikopoulos, E. Tsigaridas, H. Zafeiropoulos, Geometric algorithms for sampling the flux space of metabolic networks, SoCG 21. + + * @tparam Polytope convex polytope type + * @tparam RandomNumberGenerator random number generator type + * @tparam MT matrix type + * @tparam Point cartensian point type + * @tparam WalkTypePolicy random walk type +*/ +template +< + typename Polytope, + typename RandomNumberGenerator, + typename MT, + typename Point, + typename WalkTypePolicy +> +bool perform_mmcs_step(Polytope &P, + RandomNumberGenerator &rng, + unsigned int const& walk_length, + unsigned int const& target_ess, + unsigned int const& max_num_samples, + unsigned int const& window, + unsigned int &Neff_sampled, + unsigned int &total_samples, + unsigned int const& num_rounding_steps, + MT &TotalRandPoints, + const Point &starting_point, + unsigned int const& nburns, + bool request_rounding, + WalkTypePolicy &WalkType) +{ + typedef typename Polytope::NT NT; + typedef typename Polytope::VT VT; + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > Walk; + + bool done = false; + unsigned int points_to_sample = target_ess; + int min_eff_samples; + total_samples = 0; + MT winPoints(P.dimension(), window); + Point q(P.dimension()); + + Point p = starting_point; + + if (request_rounding) + { + TotalRandPoints.setZero(num_rounding_steps, P.dimension()); + } + else + { + TotalRandPoints.setZero(max_num_samples, P.dimension()); + } + + Walk walk(P, p, rng, WalkType.param); + ESSestimator estimator(window, P.dimension()); + + walk.template parameters_burnin(P, p, 10 + int(std::log(NT(P.dimension()))), 10, rng); + + while (!done) + { + walk.template get_starting_point(P, p, q, 10, rng); + for (int i = 0; i < window; i++) + { + walk.template apply(P, q, walk_length, rng); + winPoints.col(i) = q.getCoefficients(); + } + estimator.update_estimator(winPoints); + total_samples += window; + if (total_samples >= TotalRandPoints.rows()) + { + if (total_samples > TotalRandPoints.rows()) + { + TotalRandPoints.conservativeResize(total_samples, P.dimension()); + } + if (request_rounding || total_samples >= max_num_samples) + { + done = true; + } + } + TotalRandPoints.block(total_samples - window, 0, window, P.dimension()) = winPoints.transpose(); + if (done || total_samples >= points_to_sample) + { + estimator.estimate_effective_sample_size(); + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + if (done && min_eff_samples < target_ess) + { + Neff_sampled = min_eff_samples; + return false; + } + if (min_eff_samples >= target_ess) + { + Neff_sampled = min_eff_samples; + return true; + } + if (min_eff_samples > 0) + { + points_to_sample += (total_samples / min_eff_samples) * (target_ess - min_eff_samples) + 100; + } + else + { + points_to_sample = total_samples + 100; + } + } + } +} + +#endif diff --git a/include/sampling/parallel_mmcs.hpp b/include/sampling/parallel_mmcs.hpp new file mode 100644 index 000000000..fa0abbdaf --- /dev/null +++ b/include/sampling/parallel_mmcs.hpp @@ -0,0 +1,203 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef PARALLEL_MMCS_HPP +#define PARALLEL_MMCS_HPP + + +#include +#include +#include +#include "diagnostics/ess_window_updater.hpp" + +/** + The class implements a single step of the Parallel Multiphase Monte Carlo Sampling algorithm + given in, + + A. Chalkis, V. Fisikopoulos, E. Tsigaridas, H. Zafeiropoulos, Geometric algorithms for sampling the flux space of metabolic networks, SoCG 21. + + * @tparam WalkTypePolicy random walk type + * @tparam Polytope convex polytope type + * @tparam RandomNumberGenerator random number generator type + * @tparam MT matrix type + * @tparam Point cartensian point type + * @tparam NT number type +*/ +template +< + typename WalkTypePolicy, + typename Polytope, + typename RandomNumberGenerator, + typename MT, + typename Point, + typename NT +> +bool perform_parallel_mmcs_step(Polytope &P, + RandomNumberGenerator &rng, + unsigned int const& walk_length, + unsigned int const& target_ess, + unsigned int const& max_num_samples, + unsigned int const& window, + unsigned int &Neff_sampled, + unsigned int &total_samples, + unsigned int const& num_rounding_steps, + MT &TotalRandPoints, + const Point &starting_point, + unsigned int const& nburns, + unsigned int const& num_threads, + bool request_rounding, + NT L) +{ + typedef typename Polytope::VT VT; // vector type + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > Walk; + + typedef typename WalkTypePolicy::template thread_parameters + < + NT, + Point + > _thread_parameters; + + omp_set_num_threads(num_threads); + std::vector num_starting_points_per_thread(num_threads, 0); + std::vector bound_on_num_points_per_thread(num_threads, 0); + std::vector num_generated_points_per_thread(num_threads, 0); + unsigned int jj = 0, d = P.dimension(), m = P.num_of_hyperplanes(); + bool complete = false; + + while (jj < nburns) + { + for (unsigned int i = 0; i < num_threads; i++) + { + num_starting_points_per_thread[i]++; + bound_on_num_points_per_thread[i] += window; + jj++; + } + } + + std::vector winPoints_per_thread(num_threads, MT::Zero(d, window)); + std::vector TotalRandPoints_per_thread(num_threads); + + ESSestimator estimator(window, P.dimension()); + + bool done = false, done_all = false; + unsigned int points_to_sample = target_ess; + int min_eff_samples; + total_samples = 0; + + Point pp = starting_point; + for (unsigned int i = 0; i < num_threads; i++) + { + TotalRandPoints_per_thread[i].setZero(bound_on_num_points_per_thread[i], d); + } + unsigned int upper_bound_on_total_num_of_samples; + if (request_rounding) + { + upper_bound_on_total_num_of_samples = num_rounding_steps; + } + else + { + upper_bound_on_total_num_of_samples = max_num_samples; + } + TotalRandPoints.resize(0, 0); + Walk walk(P, L); + + _thread_parameters random_walk_parameters(d, m); + walk.template parameters_burnin(P, pp, 10 + int(std::log(NT(d))), 10, rng, random_walk_parameters); + Point const p = pp; + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + + for (unsigned int it = 0; it < num_starting_points_per_thread[thread_index]; it++) + { + if (done_all) + { + break; + } + walk.template get_starting_point(P, p, thread_random_walk_parameters, 10, rng); + for (int i = 0; i < window; i++) + { + walk.template apply(P, thread_random_walk_parameters, walk_length, rng); + winPoints_per_thread[thread_index].col(i) = thread_random_walk_parameters.p.getCoefficients(); + } + + #pragma omp critical + { + estimator.update_estimator(winPoints_per_thread[thread_index]); + } + + num_generated_points_per_thread[thread_index] += window; + + #pragma omp critical + { + total_samples += window; + } + #pragma omp single + { + if (total_samples >= upper_bound_on_total_num_of_samples) + { + done = true; + } + } + TotalRandPoints_per_thread[thread_index].block(num_generated_points_per_thread[thread_index] - window, 0, window, d) = winPoints_per_thread[thread_index].transpose(); + #pragma omp single + { + if (done || (total_samples >= points_to_sample)) + { + estimator.estimate_effective_sample_size(); + + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + if (done && min_eff_samples < target_ess) + { + Neff_sampled = min_eff_samples; + done_all = true; + } + if (min_eff_samples >= target_ess) + { + complete = true; + Neff_sampled = min_eff_samples; + done_all = true; + } + if (min_eff_samples > 0 && !done_all) + { + points_to_sample += (total_samples / min_eff_samples) * (target_ess - min_eff_samples) + 100; + } + else if (!done_all) + { + points_to_sample = total_samples + 100; + } + } + } + } + } + + estimator.estimate_effective_sample_size(); + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + Neff_sampled = min_eff_samples; + if (min_eff_samples >= target_ess) + { + complete = true; + } + + unsigned int current_num_samples = 0; + for (unsigned int i = 0; i < num_threads; i++) + { + TotalRandPoints.conservativeResize(TotalRandPoints.rows() + num_generated_points_per_thread[i], d); + TotalRandPoints.block(current_num_samples, 0, num_generated_points_per_thread[i], d) = TotalRandPoints_per_thread[i].block(0, 0, num_generated_points_per_thread[i], d); + current_num_samples += num_generated_points_per_thread[i]; + TotalRandPoints_per_thread[i].resize(0, 0); + } + + return complete; +} + +#endif